Housekeeping: offene Dev-Dateien eingecheckt + Sidebar „Bestellungen"→„Versand"
- Sidebar-Menü „Bestellungen" in „Versand" umbenannt (layout-sidenav), sonst nur Blade-Formatierung. - Entwicklungspläne in ISO-Datumsformat umbenannt (entwicklungsplan-aktualisiert-*). - dev/dhl-for-woocommerce/ (DHL-Plugin) + dev/partnercenter-api.zip + PastedGraphic hinzugefügt. - .claude/ bewusst NICHT eingecheckt (lokale Claude-Code-Konfiguration). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c7ddf7906e
commit
0f641ef33a
154 changed files with 23131 additions and 8 deletions
144
dev/dhl-for-woocommerce/includes/REST_API/API_Client.php
Normal file
144
dev/dhl-for-woocommerce/includes/REST_API/API_Client.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API;
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
|
||||
/**
|
||||
* A generic REST API client implementation.
|
||||
*
|
||||
* This class does not expose any public methods. It is intended to be extended to create real client classes,
|
||||
* alleviating most of the work involved in using drivers, auth handlers, creating request and response objects as
|
||||
* well as ensuring proper request URL integrity.
|
||||
*
|
||||
* This class provides the following functionality:
|
||||
* - protected methods for sending GET, POST and DELETE requests
|
||||
* - a base URL property that is prepended to routes before creating and sending requests
|
||||
* - implements usage of a REST API driver for sending requests
|
||||
* - implements optional usage of an auth handler to authorize requests before sending them via the driver
|
||||
*
|
||||
* Example usage:
|
||||
* ```
|
||||
* class My_API_Client extends API_Client {
|
||||
*
|
||||
* public function do_foo_bar( ) {
|
||||
* $response = $this->post( 'foo/bar, array('some' => 'data') );
|
||||
*
|
||||
* if ($response->status === 200) {
|
||||
* return $response->body;
|
||||
* }
|
||||
*
|
||||
* throw new Exception($response->body->error_msg);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
class API_Client
|
||||
{
|
||||
/**
|
||||
* The base URL for the REST API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base_url;
|
||||
|
||||
/**
|
||||
* The REST API driver to use.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* The authorization driver to use.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Auth_Interface|null
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $base_url The base URL for the REST API.
|
||||
* @param API_Driver_Interface $driver The REST API driver to use.
|
||||
* @param API_Auth_Interface|null $auth Optional authorization driver to use.
|
||||
*/
|
||||
public function __construct(string $base_url, API_Driver_Interface $driver, ?API_Auth_Interface $auth = null)
|
||||
{
|
||||
$this->base_url = $base_url;
|
||||
$this->driver = $driver;
|
||||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
protected function get($route, array $params = [], array $headers = [], array $cookies = [])
|
||||
{
|
||||
return $this->send_request(Request::TYPE_GET, $route, $params, '', $headers, $cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
protected function post($route, $body = '', array $headers = [], array $cookies = [])
|
||||
{
|
||||
return $this->send_request(Request::TYPE_POST, $route, [], $body, $headers, $cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
protected function delete($route, $body = '', array $headers = [], array $cookies = [])
|
||||
{
|
||||
return $this->send_request(Request::TYPE_DELETE, $route, [], $body, $headers, $cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request using the internal driver.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param int $type The request type, either {@link TYPE_GET} or {@link TYPE_POST}.
|
||||
* @param string $route The request route, relative to the REST API's base URL.
|
||||
* @param array $params The GET params for this request.
|
||||
* @param mixed|null $body The body of the request.
|
||||
* @param array $headers The request headers to send.
|
||||
* @param array $cookies The request cookies to send.
|
||||
* @return Response The response.
|
||||
*/
|
||||
protected function send_request(
|
||||
$type,
|
||||
$route,
|
||||
array $params = [],
|
||||
$body = '',
|
||||
array $headers = [],
|
||||
array $cookies = []
|
||||
) {
|
||||
// Generate the full URL and the request object
|
||||
$full_url = URL_Utils::merge_url_and_route($this->base_url, $route);
|
||||
$request = new Request($type, $full_url, $params, $body, $headers, $cookies);
|
||||
|
||||
// If we have an authorization driver, authorize the request
|
||||
if ($this->auth !== null) {
|
||||
$request = $this->auth->authorize($request);
|
||||
}
|
||||
|
||||
// Send the request using the driver and obtain the response
|
||||
$response = $this->driver->send($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
295
dev/dhl-for-woocommerce/includes/REST_API/Deutsche_Post/Auth.php
Normal file
295
dev/dhl-for-woocommerce/includes/REST_API/Deutsche_Post/Auth.php
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Deutsche_Post;
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Request;
|
||||
use PR\DHL\REST_API\URL_Utils;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* The authorization controller for Deutsche Post.
|
||||
*
|
||||
* The Deutsche Post API requires that requests send an "Authorization: Bearer 123456" header, where "123456" is an
|
||||
* access code. That access code is obtained from the REST API itself by sending the client ID and client secret,
|
||||
* encoded in a base64 string. The REST API should respond with a token, which will contain the code, its expiry,
|
||||
* type, etc.
|
||||
*
|
||||
* So the process for authorization involves first obtaining the token, storing it locally and then using it to
|
||||
* authorize regular REST API requests. This class stores the token in a transient with an expiry time that matches
|
||||
* the expiry time of the token as indicated by the Deutsche Post REST API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
* @see https://api-qa.deutschepost.com/dpi-apidoc/#/reference/authentication/access-token/get-access-token
|
||||
*/
|
||||
class Auth implements API_Auth_Interface
|
||||
{
|
||||
/**
|
||||
* The Deutsche POST route for obtaining an access token.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const AUTH_ROUTE = 'dpi/v1/auth/accesstoken';
|
||||
|
||||
/**
|
||||
* The Deutsche POST route for revoking an access token.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const REVOKE_ROUTE = 'dpi/v1/auth/accesstoken/revoke';
|
||||
|
||||
/**
|
||||
* The Deutsche POST header where the client ID and secret are included when fetching the access token.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const H_AUTH_CREDENTIALS = 'Authorization';
|
||||
|
||||
/**
|
||||
* The Deutsche POST authorization header where request credentials and access tokens are included.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const H_AUTH_TOKEN = 'Authorization';
|
||||
|
||||
/**
|
||||
* The Deutsche Post authorization header where the 3rd party vendor ID is included.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const H_3PV_ID = 'ThirdPartyVendor-ID';
|
||||
|
||||
/**
|
||||
* The Deutsche Post 3rd party vendor ID to include in the corresponding header.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const V_3PV_ID = '3pv_woocommerce';
|
||||
|
||||
/**
|
||||
* The driver to use for obtaining and revoking the access token.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* The client's ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $client_id;
|
||||
|
||||
/**
|
||||
* The authentication secret for the client.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $client_secret;
|
||||
|
||||
/**
|
||||
* The name of the transient to use for caching the access token.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $transient;
|
||||
|
||||
/**
|
||||
* The cached access token.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* The Deutsche Post REST API base URL.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $api_url;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param API_Driver_Interface $driver The driver to use for obtaining and revoking the access token.
|
||||
* @param string $api_url The Deutsche Post REST API base URL.
|
||||
* @param string $client_id The client's ID.
|
||||
* @param string $client_secret The authentication secret for the client.
|
||||
* @param string $transient The name of the transient to use for caching the access token.
|
||||
*/
|
||||
public function __construct(API_Driver_Interface $driver, $api_url, $client_id, $client_secret, $transient)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
$this->api_url = $api_url;
|
||||
$this->client_id = $client_id;
|
||||
$this->client_secret = $client_secret;
|
||||
$this->transient = $transient;
|
||||
|
||||
// Load the token from the transient cache
|
||||
$this->load_token();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function authorize(Request $request)
|
||||
{
|
||||
// Check if we have a token - a token is ALWAYS needed
|
||||
if (empty($this->token)) {
|
||||
// If not, request one from the REST API
|
||||
$token = $this->request_token();
|
||||
// Cache it for subsequent requests
|
||||
$this->save_token($token);
|
||||
}
|
||||
|
||||
$type = $this->token->token_type;
|
||||
$code = $this->token->access_token;
|
||||
|
||||
$request->headers[static::H_AUTH_TOKEN] = $type.' '.$code;
|
||||
$request->headers[static::H_3PV_ID] = static::V_3PV_ID;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an access token from the API.
|
||||
*
|
||||
* Sends the client ID and client secret to the REST API to obtain the access token. The access token is then
|
||||
* used to authorize all other requests.
|
||||
* See https://api-qa.deutschepost.com/dpi-apidoc/#/reference/authentication/access-token/get-access-token
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return object The token object.
|
||||
*
|
||||
* @throws RuntimeException If failed to retrieve the access token.
|
||||
*/
|
||||
public function request_token()
|
||||
{
|
||||
// Base64 encode the "<client_id>:<client_secret>" and send as the "Authorization" header
|
||||
$auth_str_64 = base64_encode($this->client_id.':'.$this->client_secret);
|
||||
$headers = [static::H_AUTH_CREDENTIALS => 'Basic '.$auth_str_64];
|
||||
|
||||
// Prepare the full request URL
|
||||
$full_url = URL_Utils::merge_url_and_route($this->api_url, static::AUTH_ROUTE);
|
||||
|
||||
// Send the authorization request to obtain the access token
|
||||
$request = new Request(Request::TYPE_GET, $full_url, [], '', $headers);
|
||||
$response = $this->driver->send($request);
|
||||
|
||||
// If the status code is not 200, throw an error with the raw response body
|
||||
if ($response->status !== 200) {
|
||||
$response_body = json_decode($response->body);
|
||||
throw new RuntimeException(esc_html($response_body->detail));
|
||||
}
|
||||
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes the access token.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string The response body.
|
||||
*/
|
||||
public function revoke()
|
||||
{
|
||||
// Do nothing if we didn't already have a token
|
||||
if (empty($this->token) || empty($this->token->access_token)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Prepare the full request URL
|
||||
$full_url = URL_Utils::merge_url_and_route($this->api_url, static::REVOKE_ROUTE);
|
||||
// Create the request
|
||||
$params = ['token' => $this->token->access_token];
|
||||
$request = new Request(Request::TYPE_GET, $full_url, $params);
|
||||
|
||||
// Send the request
|
||||
$response = $this->driver->send($request);
|
||||
|
||||
if ($response->status !== 200 && $response->status !== 401) {
|
||||
throw new RuntimeException('Failed to revoke the Deutsche Post access token');
|
||||
}
|
||||
|
||||
// Delete the cached token
|
||||
$this->delete_token();
|
||||
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the connection with a given client ID and secret.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $client_id The client ID.
|
||||
* @param string $client_secret The client secret.
|
||||
* @return object
|
||||
*/
|
||||
public function test_connection($client_id, $client_secret)
|
||||
{
|
||||
// Backup the client credentials
|
||||
$backup_client_id = $this->client_id;
|
||||
$backup_client_secret = $this->client_secret;
|
||||
|
||||
// Set params as credentials
|
||||
$this->client_id = $client_id;
|
||||
$this->client_secret = $client_secret;
|
||||
|
||||
// Send the request
|
||||
$token = $this->request_token();
|
||||
|
||||
// Restore the credentials
|
||||
$this->client_id = $backup_client_id;
|
||||
$this->client_secret = $backup_client_secret;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the access token.
|
||||
*
|
||||
* @param object $token The token to save.
|
||||
*/
|
||||
public function save_token($token)
|
||||
{
|
||||
$expires_in = isset($token->expires_in)
|
||||
? $token->expires_in
|
||||
: time() + DAY_IN_SECONDS;
|
||||
|
||||
set_transient($this->transient, $token, $expires_in);
|
||||
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the access token.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function load_token()
|
||||
{
|
||||
return $this->token = get_transient($this->transient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the cached access token.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function delete_token()
|
||||
{
|
||||
return delete_transient($this->transient);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,465 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Deutsche_Post;
|
||||
|
||||
use Exception;
|
||||
use PR\DHL\REST_API\API_Client;
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* The API client for Deutsche Post.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
class Client extends API_Client
|
||||
{
|
||||
/**
|
||||
* The customer EKP.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ekp;
|
||||
|
||||
/**
|
||||
* The contact name to use for creating orders.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contact_name;
|
||||
|
||||
/**
|
||||
* The contact phone number to use for creating orders for "Express" customers
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contact_phone;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $ekp The customer EKP.
|
||||
* @param string $contact_name The contact name to use for creating orders.
|
||||
* @param string $contact_phone The contact phone number to use for creating orders.
|
||||
*/
|
||||
public function __construct($ekp, $contact_name, $contact_phone, $base_url, API_Driver_Interface $driver, ?API_Auth_Interface $auth = null)
|
||||
{
|
||||
parent::__construct($base_url, $driver, $auth);
|
||||
|
||||
$this->ekp = $ekp;
|
||||
$this->contact_name = $contact_name;
|
||||
$this->contact_phone = $contact_phone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an item on the remote API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param Item_Info $item_info The information of the item to be created.
|
||||
* @return stdClass The item information as returned by the remote API.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create_item(Item_Info $item_info)
|
||||
{
|
||||
// Prepare the request route and data
|
||||
$route = $this->customer_route('items');
|
||||
$data = $this->item_info_to_request_data($item_info);
|
||||
|
||||
// Send the request and get the response
|
||||
$response = $this->post($route, $data);
|
||||
|
||||
// Return the response body on success
|
||||
if ($response->status === 200) {
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
// Otherwise throw an exception using the response's error messages
|
||||
$message = ! empty($response->body->messages)
|
||||
? implode(', ', $response->body->messages)
|
||||
: strval($response->body);
|
||||
|
||||
throw new Exception(
|
||||
// Translators: %s is the error message from the API.
|
||||
sprintf(esc_html__('API error: %s', 'dhl-for-woocommerce'), esc_html($message))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an item from the remote API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param int $item_id The ID of the item to delete.
|
||||
* @return stdClass The response.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete_item($item_id)
|
||||
{
|
||||
// Compute the route to the API endpoint
|
||||
$route = $this->customer_route('items/'.$item_id);
|
||||
|
||||
// Send the DELETE request
|
||||
$response = $this->delete($route);
|
||||
|
||||
// Return the response body on success
|
||||
if ($response->status === 200) {
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
// Otherwise throw an exception using the response's error messages
|
||||
$message = ! empty($response->body->messages)
|
||||
? implode(', ', $response->body->messages)
|
||||
: strval($response->body);
|
||||
|
||||
throw new Exception(
|
||||
// Translators: %s is the error message from the API.
|
||||
sprintf(esc_html__('API error: %s', 'dhl-for-woocommerce'), esc_html($message))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the label for a DHL item, by its barcode.
|
||||
*
|
||||
* @param string $item_barcode The barcode of the item whose label to retrieve.
|
||||
* @return string The raw PDF data for the item's label.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_item_label($item_barcode)
|
||||
{
|
||||
$route = sprintf('items/%s/label', $item_barcode);
|
||||
|
||||
$response = $this->get(
|
||||
$this->customer_route($route),
|
||||
[],
|
||||
[
|
||||
'Accept' => 'application/pdf',
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->status === 200) {
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
$message = ! empty($response->body->messages)
|
||||
? implode(', ', $response->body->messages)
|
||||
: strval($response->body);
|
||||
|
||||
throw new Exception(
|
||||
// Translators: %s is the error message from the API.
|
||||
sprintf(esc_html__('API error: %s', 'dhl-for-woocommerce'), esc_html($message))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves items from the remote API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array The list of items.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_items()
|
||||
{
|
||||
$response = $this->get($this->customer_route('items'));
|
||||
|
||||
if ($response->status === 200) {
|
||||
return (array) $response->body;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
// Translators: %s is the error message from the API.
|
||||
esc_html__('Failed to get items from the API: %s', 'dhl-for-woocommerce'),
|
||||
esc_html(implode(', ', $response->body->messages))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current DHL order, or an existing one if an ID is given.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param int|null $orderId Optional DHL order ID.
|
||||
* @return array
|
||||
*/
|
||||
public function get_order($orderId = null)
|
||||
{
|
||||
$current = get_option('pr_dhl_dp_order', $this->get_default_order_info());
|
||||
|
||||
if (empty($orderId)) {
|
||||
return $current;
|
||||
}
|
||||
|
||||
return get_option('pr_dhl_dp_order_'.$orderId, $current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the current DHL order.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $item_barcode The barcode of the item to add.
|
||||
* @param string $wc_order The ID of the WooCommerce order.
|
||||
*/
|
||||
public function add_item_to_order($item_barcode, $wc_order)
|
||||
{
|
||||
$order = $this->get_order();
|
||||
|
||||
$order['items'][$item_barcode] = $wc_order;
|
||||
|
||||
update_option('pr_dhl_dp_order', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the current DHL order.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $item_barcode The barcode of the item to add.
|
||||
*/
|
||||
public function remove_item_from_order($item_barcode)
|
||||
{
|
||||
$order = $this->get_order();
|
||||
|
||||
unset($order['items'][$item_barcode]);
|
||||
|
||||
update_option('pr_dhl_dp_order', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current order.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function reset_current_order()
|
||||
{
|
||||
update_option('pr_dhl_dp_order', $this->get_default_order_info());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Deutsche Post order for the current local order of items.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return object The response data.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create_order($copy_count = 1)
|
||||
{
|
||||
$order = $this->get_order();
|
||||
$items = $order['items'];
|
||||
$barcodes = array_keys($items);
|
||||
$copy_count = intval($copy_count);
|
||||
|
||||
$route = $this->customer_route('orders');
|
||||
$data = [
|
||||
'itemBarcodes' => $barcodes,
|
||||
'paperwork' => [
|
||||
'awbCopyCount' => $copy_count,
|
||||
'contactName' => $this->contact_name,
|
||||
'telephoneNumber' => $this->contact_phone,
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->post($route, $data);
|
||||
|
||||
if ($response->status === 200) {
|
||||
$this->close_order($response->body);
|
||||
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
// Translators: %s is the error message from the API.
|
||||
esc_html__('Failed to create order: %s', 'dhl-for-woocommerce'),
|
||||
esc_html(implode(', ', $response->body->messages))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current order with information received from the REST API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param object $info The information received from the REST API.
|
||||
*/
|
||||
protected function close_order($info)
|
||||
{
|
||||
// Get the current order from the options
|
||||
$order = $this->get_order();
|
||||
// Save the response info in the order
|
||||
$order['id'] = $info->orderId;
|
||||
$order['shipments'] = $info->shipments;
|
||||
// Save the order status if it's given
|
||||
if (! empty($info->orderStatus)) {
|
||||
$order['status'] = $info->orderStatus;
|
||||
}
|
||||
// Save the order in a new option
|
||||
update_option('pr_dhl_dp_order_'.$info->orderId, $order);
|
||||
// Reset the current order
|
||||
$this->reset_current_order();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the label for a given AWB
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $awb The AWB of the shipment.
|
||||
* @return string The raw PDF data for the AWB label.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_awb_label($awb)
|
||||
{
|
||||
$response = $this->get(
|
||||
sprintf('dpi/shipping/v1/shipments/%s/awblabels', $awb),
|
||||
[],
|
||||
[
|
||||
'Accept' => 'application/pdf',
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->status === 200) {
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
$message = ! empty($response->body->messages)
|
||||
? implode(', ', $response->body->messages)
|
||||
: strval($response->body);
|
||||
|
||||
throw new Exception(
|
||||
// Translators: %s is the error message from the API.
|
||||
sprintf(esc_html__('API error: %s', 'dhl-for-woocommerce'), esc_html($message))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves tracking info for a shipment by its AWB
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $awb The AWB of the shipment.
|
||||
* @return object The tracking info as returned by the remote API.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_shipment_tracking_info($awb)
|
||||
{
|
||||
$response = $this->get(sprintf('dpi/tracking/v1/trackings/awb/%s', $awb));
|
||||
|
||||
if ($response->status === 200) {
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
$message = ! empty($response->body->messages)
|
||||
? implode(', ', $response->body->messages)
|
||||
: strval($response->body);
|
||||
|
||||
throw new Exception(
|
||||
// Translators: %s is the error message from the API.
|
||||
sprintf(esc_html__('API error: %s', 'dhl-for-woocommerce'), esc_html($message))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an item info object into a request data array.
|
||||
*
|
||||
* @param Item_Info $item_info The item info object to transform.
|
||||
* @return array The request data for the given item info object.
|
||||
*/
|
||||
protected function item_info_to_request_data(Item_Info $item_info)
|
||||
{
|
||||
$contents = [];
|
||||
foreach ($item_info->contents as $content_info) {
|
||||
|
||||
$item_desc = $content_info['description'];
|
||||
if (! empty($content_info['description_export'])) {
|
||||
$item_desc = $content_info['description_export'];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'contentPieceAmount' => $content_info['qty'],
|
||||
'contentPieceDescription' => $item_desc,
|
||||
'contentPieceIndexNumber' => $content_info['product_id'],
|
||||
'contentPieceNetweight' => $content_info['weight'],
|
||||
'contentPieceOrigin' => $content_info['origin'],
|
||||
'contentPieceValue' => $content_info['value'],
|
||||
'contentPieceHsCode' => trim($content_info['hs_code']),
|
||||
];
|
||||
// Only include HS code if it's not empty
|
||||
if (empty($data['contentPieceHsCode'])) {
|
||||
unset($data['contentPieceHsCode']);
|
||||
}
|
||||
$contents[] = $data;
|
||||
}
|
||||
|
||||
return [
|
||||
'serviceLevel' => $item_info->shipment['service_level'],
|
||||
'product' => $item_info->shipment['product'],
|
||||
'custRef' => $item_info->shipment['label_ref'],
|
||||
'custRef2' => $item_info->shipment['label_ref_2'],
|
||||
'shipmentAmount' => $item_info->shipment['value'],
|
||||
'shipmentCurrency' => $item_info->shipment['currency'],
|
||||
'shipmentGrossWeight' => $item_info->shipment['weight'],
|
||||
'shipmentNaturetype' => $item_info->shipment['nature_type'],
|
||||
'returnItemWanted' => $item_info->shipment['packet_return'],
|
||||
'importerTaxId' => $item_info->shipment['importer_taxid'],
|
||||
'senderTaxId' => $item_info->shipment['sender_taxid'],
|
||||
'recipient' => $item_info->recipient['name'],
|
||||
'recipientPhone' => $item_info->recipient['phone'],
|
||||
'recipientEmail' => $item_info->recipient['email'],
|
||||
'addressLine1' => $item_info->recipient['address_1'],
|
||||
'addressLine2' => $item_info->recipient['address_2'],
|
||||
'city' => $item_info->recipient['city'],
|
||||
'postalCode' => $item_info->recipient['postcode'],
|
||||
'state' => $item_info->recipient['state'],
|
||||
'destinationCountry' => $item_info->recipient['country'],
|
||||
'contents' => $contents,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_order_info()
|
||||
{
|
||||
return [
|
||||
'id' => null,
|
||||
'status' => null,
|
||||
'items' => [],
|
||||
'shipments' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an API route with the customer namespace and EKP.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $route The route to prepare.
|
||||
* @return string
|
||||
*/
|
||||
protected function customer_route($route)
|
||||
{
|
||||
return sprintf('dpi/shipping/v1/customers/%s/%s', $this->ekp, $route);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Deutsche_Post;
|
||||
|
||||
use Exception;
|
||||
use PR\DHL\Utils\Args_Parser;
|
||||
|
||||
/**
|
||||
* A class that represents a Deutsche Post item, which corresponds to a WooCommerce order.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
class Item_Info
|
||||
{
|
||||
/**
|
||||
* The array of shipment information.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $shipment;
|
||||
|
||||
/**
|
||||
* The array of order recipient information.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $recipient;
|
||||
|
||||
/**
|
||||
* The array of content item information sub-arrays.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
public $contents;
|
||||
|
||||
/**
|
||||
* The units of measurement used for weights in the input args.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $weightUom;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param array $args The arguments to parse.
|
||||
* @param string $weightUom The units of measurement used for weights in the input args.
|
||||
*
|
||||
* @throws Exception If some data in $args did not pass validation.
|
||||
*/
|
||||
public function __construct($args, $weightUom = 'g')
|
||||
{
|
||||
$this->weightUom = $weightUom;
|
||||
$this->parse_args($args, $weightUom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the arguments and sets the instance's properties.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param array $args The arguments to parse.
|
||||
*
|
||||
* @throws Exception If some data in $args did not pass validation.
|
||||
*/
|
||||
protected function parse_args($args)
|
||||
{
|
||||
$settings = $args['dhl_settings'];
|
||||
$recipient_info = $args['shipping_address'] + $settings;
|
||||
$shipping_info = $args['order_details'] + $settings;
|
||||
$items_info = $args['items'];
|
||||
|
||||
$this->shipment = Args_Parser::parse_args($shipping_info, $this->get_shipment_info_schema());
|
||||
$this->recipient = Args_Parser::parse_args($recipient_info, $this->get_recipient_info_schema());
|
||||
$this->contents = [];
|
||||
|
||||
$this->contents = [];
|
||||
foreach ($items_info as $item_info) {
|
||||
$this->contents[] = Args_Parser::parse_args($item_info, $this->get_content_item_info_schema());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the args scheme to use with {@link Args_Parser} for parsing shipment info.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_shipment_info_schema()
|
||||
{
|
||||
// Closures in PHP 5.3 do not inherit class context
|
||||
// So we need to copy $this into a lexical variable and pass it to closures manually
|
||||
$self = $this;
|
||||
|
||||
return [
|
||||
'dhl_label_ref' => [
|
||||
'rename' => 'label_ref',
|
||||
'sanitize' => function ($label_ref) use ($self) {
|
||||
|
||||
return $self->string_length_sanitization($label_ref, 28);
|
||||
},
|
||||
],
|
||||
'dhl_label_ref_2' => [
|
||||
'rename' => 'label_ref_2',
|
||||
'sanitize' => function ($label_ref_2) use ($self) {
|
||||
|
||||
return $self->string_length_sanitization($label_ref_2, 28);
|
||||
},
|
||||
],
|
||||
'dhl_product' => [
|
||||
'rename' => 'product',
|
||||
'error' => esc_html__('DHL "Product" is empty!', 'dhl-for-woocommerce'),
|
||||
'sanitize' => function ($product) {
|
||||
|
||||
$product_info = explode('-', $product);
|
||||
$product = $product_info[0];
|
||||
|
||||
return $product;
|
||||
},
|
||||
],
|
||||
'dhl_service_level' => [
|
||||
'rename' => 'service_level',
|
||||
'default' => 'PRIORITY',
|
||||
'validate' => function ($level) {
|
||||
if ($level !== 'STANDARD' && $level !== 'PRIORITY' && $level !== 'REGISTERED') {
|
||||
throw new Exception(esc_html__('Order "Service Level" is invalid', 'dhl-for-woocommerce'));
|
||||
}
|
||||
},
|
||||
],
|
||||
'weight' => [
|
||||
'error' => esc_html__('Order "Weight" is empty!', 'dhl-for-woocommerce'),
|
||||
'validate' => function ($weight) {
|
||||
if (! is_numeric(wc_format_decimal($weight))) {
|
||||
throw new Exception(esc_html__('The order "Weight" must be a number', 'dhl-for-woocommerce'));
|
||||
}
|
||||
},
|
||||
'sanitize' => function ($weight) use ($self) {
|
||||
$weight = $self->maybe_convert_to_grams($weight, $self->weightUom);
|
||||
|
||||
return $weight;
|
||||
},
|
||||
],
|
||||
'currency' => [
|
||||
'error' => esc_html__('Shop "Currency" is empty!', 'dhl-for-woocommerce'),
|
||||
],
|
||||
'total_value' => [
|
||||
'rename' => 'value',
|
||||
'error' => esc_html__('Shipment "Value" is empty!', 'dhl-for-woocommerce'),
|
||||
'validate' => function ($value) {
|
||||
if (! is_numeric($value)) {
|
||||
throw new Exception(esc_html__('The order "value" must be a number', 'dhl-for-woocommerce'));
|
||||
}
|
||||
},
|
||||
'sanitize' => function ($value) use ($self) {
|
||||
|
||||
return $self->float_round_sanitization($value, 2);
|
||||
},
|
||||
],
|
||||
'nature_type' => [
|
||||
'rename' => 'nature_type',
|
||||
'default' => 'SALE_GOODS',
|
||||
],
|
||||
'packet_return' => [
|
||||
'default' => false,
|
||||
'sanitize' => function ($return) {
|
||||
|
||||
return ($return == 'yes') ? true : false;
|
||||
},
|
||||
],
|
||||
'importer_taxid' => [
|
||||
'default' => '',
|
||||
'validate' => function ($value) {
|
||||
|
||||
if (! empty($value)) {
|
||||
|
||||
if (! ctype_alnum($value)) {
|
||||
throw new Exception(esc_html__('The importer customs reference "value" must be an alphanumeric', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (strlen($value) > 35) {
|
||||
throw new Exception(esc_html__('The importer customs reference "value" must be between 1 and 35 characters long', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'sender_taxid' => [
|
||||
'default' => '',
|
||||
'validate' => function ($value) {
|
||||
|
||||
if (! empty($value)) {
|
||||
|
||||
if (! ctype_alnum($value)) {
|
||||
throw new Exception(esc_html__('The sender customs reference "value" must be an alphanumeric', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (strlen($value) > 35) {
|
||||
throw new Exception(esc_html__('The sender customs reference "value" must be between 1 and 35 characters long', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the args scheme to use with {@link Args_Parser} for parsing order recipient info.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_recipient_info_schema()
|
||||
{
|
||||
|
||||
// Closures in PHP 5.3 do not inherit class context
|
||||
// So we need to copy $this into a lexical variable and pass it to closures manually
|
||||
$self = $this;
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'default' => '',
|
||||
'error' => esc_html__('Recipient is empty!', 'dhl-for-woocommerce'),
|
||||
'sanitize' => function ($name) use ($self) {
|
||||
|
||||
return $self->string_length_sanitization($name, 30);
|
||||
},
|
||||
],
|
||||
'phone' => [
|
||||
'default' => '',
|
||||
'sanitize' => function ($phone) use ($self) {
|
||||
|
||||
return $self->string_length_sanitization($phone, 15);
|
||||
},
|
||||
],
|
||||
'email' => [
|
||||
'default' => '',
|
||||
],
|
||||
'address_1' => [
|
||||
'error' => esc_html__('Shipping "Address 1" is empty!', 'dhl-for-woocommerce'),
|
||||
],
|
||||
'address_2' => [
|
||||
'default' => '',
|
||||
],
|
||||
'city' => [
|
||||
'error' => esc_html__('Shipping "City" is empty!', 'dhl-for-woocommerce'),
|
||||
],
|
||||
'postcode' => [
|
||||
'default' => '',
|
||||
],
|
||||
'state' => [
|
||||
'default' => '',
|
||||
],
|
||||
'country' => [
|
||||
'error' => esc_html__('Shipping "Country" is empty!', 'dhl-for-woocommerce'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the args scheme to use with {@link Args_Parser} for parsing order content item info.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_content_item_info_schema()
|
||||
{
|
||||
// Closures in PHP 5.3 do not inherit class context
|
||||
// So we need to copy $this into a lexical variable and pass it to closures manually
|
||||
$self = $this;
|
||||
|
||||
return [
|
||||
'hs_code' => [
|
||||
'default' => '',
|
||||
'validate' => function ($hs_code) {
|
||||
$length = is_string($hs_code) ? strlen($hs_code) : 0;
|
||||
|
||||
if (empty($length)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($length < 4 || $length > 20) {
|
||||
throw new Exception(
|
||||
esc_html__('Item HS Code must be between 4 and 20 characters long', 'dhl-for-woocommerce')
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
'item_description' => [
|
||||
'rename' => 'description',
|
||||
'default' => '',
|
||||
'sanitize' => function ($description) use ($self) {
|
||||
|
||||
return $self->string_length_sanitization($description, 33);
|
||||
},
|
||||
],
|
||||
'item_export' => [
|
||||
'rename' => 'description_export',
|
||||
'default' => '',
|
||||
'sanitize' => function ($description_export) use ($self) {
|
||||
|
||||
return $self->string_length_sanitization($description_export, 33);
|
||||
},
|
||||
],
|
||||
'product_id' => [
|
||||
'default' => '',
|
||||
],
|
||||
'sku' => [
|
||||
'default' => '',
|
||||
],
|
||||
'item_value' => [
|
||||
'rename' => 'value',
|
||||
'default' => 0,
|
||||
'sanitize' => function ($value, $args) use ($self) {
|
||||
|
||||
$qty = isset($args['qty']) && is_numeric($args['qty']) ? floatval($args['qty']) : 1;
|
||||
$total_value = floatval($value) * $qty;
|
||||
|
||||
return (string) $self->float_round_sanitization($total_value, 2);
|
||||
},
|
||||
],
|
||||
'country_origin' => [
|
||||
'rename' => 'origin',
|
||||
'default' => PR_DHL()->get_base_country(),
|
||||
],
|
||||
'qty' => [
|
||||
'default' => 1,
|
||||
],
|
||||
'item_weight' => [
|
||||
'rename' => 'weight',
|
||||
'default' => 1,
|
||||
'sanitize' => function ($weight) use ($self) {
|
||||
|
||||
$weight = $self->maybe_convert_to_grams($weight, $self->weightUom);
|
||||
|
||||
return $weight;
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given weight into grams, if necessary.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param float $weight The weight amount.
|
||||
* @param string $uom The unit of measurement of the $weight parameter..
|
||||
* @return float The potentially converted weight.
|
||||
*/
|
||||
protected function maybe_convert_to_grams($weight, $uom)
|
||||
{
|
||||
$weight = floatval(wc_format_decimal($weight));
|
||||
|
||||
switch ($uom) {
|
||||
case 'kg':
|
||||
$weight = $weight * 1000;
|
||||
break;
|
||||
case 'lb':
|
||||
$weight = $weight / 2.2;
|
||||
break;
|
||||
case 'oz':
|
||||
$weight = $weight / 35.274;
|
||||
break;
|
||||
}
|
||||
|
||||
return round($weight);
|
||||
}
|
||||
|
||||
protected function float_round_sanitization($float, $numcomma)
|
||||
{
|
||||
|
||||
$float = round(floatval($float), $numcomma);
|
||||
|
||||
return number_format($float, 2, '.', '');
|
||||
}
|
||||
|
||||
protected function string_length_sanitization($string, $max)
|
||||
{
|
||||
|
||||
$max = intval($max);
|
||||
|
||||
if (strlen($string) <= $max) {
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
return substr($string, 0, ($max - 1));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Drivers;
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Request;
|
||||
use PR\DHL\REST_API\Response;
|
||||
|
||||
/**
|
||||
* A REST API driver decorator that automatically encodes/decodes JSON in POST requests/responses respectively.
|
||||
*
|
||||
* This is a REST API driver DECORATOR class, which means that it is not a standalone driver but instead decorates
|
||||
* another driver. It does so to add JSON encoding and parsing functionality to that "inner" driver.
|
||||
*
|
||||
* It ensures that the necessary headers are sent to the remote resource that indicate that the content body is a
|
||||
* JSON string and it also ensures that incoming responses are correctly parsed as JSON strings if the remote
|
||||
* resource indicates that it is such.
|
||||
*
|
||||
* For more information on REST API drivers, refer to the documentation for the {@link API_Driver_Interface}.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
* @see API_Driver_Interface
|
||||
*/
|
||||
class JSON_API_Driver implements API_Driver_Interface
|
||||
{
|
||||
/**
|
||||
* The HTTP content type header.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const H_CONTENT_TYPE = 'Content-Type';
|
||||
|
||||
/**
|
||||
* The HTTP content type acceptance header.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const H_ACCEPT = 'Accept';
|
||||
|
||||
/**
|
||||
* The HTTP content length header.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const H_CONTENT_LENGTH = 'Content-Length';
|
||||
|
||||
/**
|
||||
* The JSON HTTP content type string.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const JSON_CONTENT_TYPE = 'application/json';
|
||||
|
||||
/**
|
||||
* The driver to decorate.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param API_Driver_Interface $driver The driver instance to decorate.
|
||||
*/
|
||||
public function __construct(API_Driver_Interface $driver)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Before invoking the inner driver's {@link send()} method, it creates a copy of the request to encode its body,
|
||||
* if necessary, and add the appropriate headers. The response from the inner driver is then decoded, if necessary,
|
||||
* and returned.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function send(Request $request)
|
||||
{
|
||||
// Encode the request before sending it
|
||||
$request = $this->encode_request($request);
|
||||
|
||||
// Delegate the actual sending to the internal driver
|
||||
$response = $this->driver->send($request);
|
||||
|
||||
// Decode the response before returning it
|
||||
$response = $this->decode_response($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the request body into a JSON string and ensures the request headers are correctly set.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param Request $request The request to encode.
|
||||
* @return Request The encoded request.
|
||||
*/
|
||||
protected function encode_request(Request $request)
|
||||
{
|
||||
// Add the header that tells the remote that we accept JSON responses
|
||||
if (empty($request->headers[static::H_ACCEPT])) {
|
||||
$request->headers[static::H_ACCEPT] = static::JSON_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
// For POST requests, encode the body and set the content type and length
|
||||
if ($request->type === Request::TYPE_POST) {
|
||||
$request->body = wp_json_encode($request->body);
|
||||
$request->headers[static::H_CONTENT_TYPE] = static::JSON_CONTENT_TYPE;
|
||||
$request->headers[static::H_CONTENT_LENGTH] = strlen($request->body);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the response is decoded from JSON.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param Response $response The response.
|
||||
* @return Response The decoded response.
|
||||
*/
|
||||
protected function decode_response(Response $response)
|
||||
{
|
||||
// Get the content type header
|
||||
$content_type = isset($response->headers[static::H_CONTENT_TYPE])
|
||||
? $response->headers[static::H_CONTENT_TYPE]
|
||||
: '';
|
||||
|
||||
// If the content type is JSON, decode the body
|
||||
if ($content_type === static::JSON_CONTENT_TYPE) {
|
||||
$response->body = json_decode($response->body);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Drivers;
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Request;
|
||||
use PR\DHL\REST_API\Response;
|
||||
use PR_DHL_WC;
|
||||
|
||||
/**
|
||||
* A REST API driver decorator that automatically logs requests and responses.
|
||||
*
|
||||
* This is a REST API driver DECORATOR class, which means that it is not a standalone driver but instead decorates
|
||||
* another driver. It does so to log the parameters and return values of the "inner" driver.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
* @see API_Driver_Interface
|
||||
*/
|
||||
class Logging_Driver implements API_Driver_Interface
|
||||
{
|
||||
/**
|
||||
* The plugin instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var PR_DHL_WC
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* The driver to decorate.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param PR_DHL_WC $plugin The plugin instance to use for logging.
|
||||
* @param API_Driver_Interface $driver The driver instance to decorate.
|
||||
*/
|
||||
public function __construct(PR_DHL_WC $plugin, API_Driver_Interface $driver)
|
||||
{
|
||||
$this->plugin = $plugin;
|
||||
$this->driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Before invoking the inner driver's {@link send()} method, it logs the request data.
|
||||
* The response returned from the inner driver is also logged, after which it is returned.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function send(Request $request)
|
||||
{
|
||||
// Log the request
|
||||
$this->log_request('Request:', $request);
|
||||
|
||||
// Send the request using the inner driver
|
||||
$response = $this->driver->send($request);
|
||||
|
||||
// Log the response
|
||||
$this->log_response('Response:', $response);
|
||||
|
||||
// Return the response from the inner driver
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a request.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $message Prefix message to include in the log.
|
||||
* @param Request $request The request to log.
|
||||
*/
|
||||
protected function log_request(string $message, Request $request)
|
||||
{
|
||||
$request_info = [
|
||||
'type' => $this->get_request_type_name($request->type),
|
||||
'url' => $request->url,
|
||||
'params' => $request->params,
|
||||
'body' => empty($request->body) ? '' : $request->body,
|
||||
];
|
||||
|
||||
$this->plugin->log_msg(sprintf('%s %s', $message, print_r($request_info, true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a response.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $message Prefix message to include in the log.
|
||||
* @param Response $response The response to log.
|
||||
*/
|
||||
protected function log_response(string $message, Response $response)
|
||||
{
|
||||
if (isset($response->headers['Content-Type']) && $response->headers['Content-Type'] === 'application/pdf') {
|
||||
$body = '[PDF data]';
|
||||
} else {
|
||||
$decoded = json_decode($response->body, true);
|
||||
$body = $decoded ?: $response->body;
|
||||
|
||||
// Remove b64 data from response body for cleaner logs.
|
||||
if (is_array($body)) {
|
||||
$body = $this->remove_b64_data($body);
|
||||
}
|
||||
}
|
||||
|
||||
$response_info = [
|
||||
'status' => $response->status,
|
||||
'body' => json_encode($body),
|
||||
];
|
||||
|
||||
$this->plugin->log_msg(sprintf('%s %s', $message, print_r($response_info, true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name for a request type.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param int $type The request type. See the constants in the {@link Request} class.
|
||||
* @return string|int The name of the request type, or the parameter if the request type is unknown.
|
||||
*/
|
||||
protected function get_request_type_name($type)
|
||||
{
|
||||
if ($type === Request::TYPE_GET) {
|
||||
return 'GET';
|
||||
}
|
||||
|
||||
if ($type === Request::TYPE_POST) {
|
||||
return 'POST';
|
||||
}
|
||||
|
||||
if ($type === Request::TYPE_DELETE) {
|
||||
return 'DELETE';
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes b64 data from response body to keep logs clean.
|
||||
*
|
||||
* @param array $data The response body array.
|
||||
* @return array The array with b64 data removed.
|
||||
*/
|
||||
protected function remove_b64_data(array $data): array
|
||||
{
|
||||
if (isset($data['items']) && is_array($data['items'])) {
|
||||
foreach ($data['items'] as $item_key => $item) {
|
||||
if (isset($item['label']['b64'])) {
|
||||
$data['items'][$item_key]['label']['b64'] = '[Base64 PDF data removed from log]';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Drivers;
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Request;
|
||||
use PR\DHL\REST_API\Response;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A REST API driver that uses WordPress's `wp_remote_*()` functions to make requests to a REST API.
|
||||
*
|
||||
* This is a relatively simply driver that encapsulates WordPress' `wp_remote_*()` family of functions into a driver
|
||||
* class, which can be used by API clients to dispatch request objects to the remote resource and receive full response
|
||||
* objects.
|
||||
*
|
||||
* For more information on REST API drivers, refer to the documentation for the {@link API_Driver_Interface}.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
* @see API_Driver_Interface
|
||||
*/
|
||||
class WP_API_Driver implements API_Driver_Interface
|
||||
{
|
||||
// Set request timeout to 30 seconds, default of 5 is to small
|
||||
const WP_REQUEST_TIMEOUT = 30;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function send(Request $request)
|
||||
{
|
||||
// Send the request
|
||||
$response = wp_remote_request(
|
||||
$request->url,
|
||||
[
|
||||
'method' => $this->get_request_type($request),
|
||||
'body' => $request->body,
|
||||
'headers' => $request->headers,
|
||||
'cookies' => $request->cookies,
|
||||
'timeout' => self::WP_REQUEST_TIMEOUT,
|
||||
'user-agent' => 'WooCommerce/'.WC_VERSION.' (WordPress/'.get_bloginfo('version').') DHL-plug-in/'.PR_DHL_VERSION,
|
||||
]
|
||||
);
|
||||
|
||||
// Check if an error occurred
|
||||
if (is_wp_error($response)) {
|
||||
throw new RuntimeException(esc_html__($response->get_error_message(), 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Retrieve the headers from the response
|
||||
$raw_headers = wp_remote_retrieve_headers($response);
|
||||
$headers = [];
|
||||
|
||||
// Ensures that the headers use the correct casing
|
||||
foreach ($raw_headers as $header => $value) {
|
||||
// 1. split the header name by dashes
|
||||
$header_parts = explode('-', $header);
|
||||
// 2. upper-case the first letter of each part
|
||||
$uc_parts = array_map('ucfirst', $header_parts);
|
||||
// 3. combine the parts back again using dashes
|
||||
$fixed_header = implode('-', $uc_parts);
|
||||
|
||||
$headers[$fixed_header] = $value;
|
||||
}
|
||||
|
||||
// Create and return the response object
|
||||
return new Response(
|
||||
$request,
|
||||
wp_remote_retrieve_response_code($response),
|
||||
wp_remote_retrieve_body($response),
|
||||
$headers,
|
||||
wp_remote_retrieve_cookies($response)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Request type.
|
||||
*
|
||||
*
|
||||
* @return string.
|
||||
*/
|
||||
public function get_request_type(Request $request)
|
||||
{
|
||||
if ($request->type === Request::TYPE_GET) {
|
||||
return 'GET';
|
||||
}
|
||||
|
||||
if ($request->type === Request::TYPE_DELETE) {
|
||||
return 'DELETE';
|
||||
}
|
||||
|
||||
return 'POST';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Interfaces;
|
||||
|
||||
use PR\DHL\REST_API\Request;
|
||||
|
||||
/**
|
||||
* Interface for objects that represent REST API authorization handlers.
|
||||
*
|
||||
* Auth handlers are classes that can take a normal request and add authorization and/or authentication information
|
||||
* to it. As such, auth handlers are used by API clients as a "filter"; the client's request is given to the auth
|
||||
* handler, which returns a new request. The client can then give that new request to its driver.
|
||||
*
|
||||
* Typical usage of an auth handler from an API client class:
|
||||
* ```
|
||||
* class Client {
|
||||
* protected $driver;
|
||||
* protected $auth;
|
||||
*
|
||||
* public function lorem_ipsum() {
|
||||
* $request = new Request( ... );
|
||||
* $request = $this->auth->authorize( $request );
|
||||
* $response = $this->driver->send( $request );
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* It's important to note that clients should ALWAYS use the request instance returned by the {@link authorize()}
|
||||
* method and should not assume that the auth handler will modify the request object given as argument. Auth handlers
|
||||
* MAY re-return the argument, but it's not guaranteed. Auth handler implementations are free to manipulate the request
|
||||
* however is necessary - add GET params, POST fields, cookies, headers, etc. - which may at times be easier done by
|
||||
* reusing the argument instance and at other times easier done by creating a brand new request instance.
|
||||
*
|
||||
* It may be the case that REST API authorization depends on some temporary token that is fetched from the REST
|
||||
* API itself. For these cases, auth handlers MAY use a driver to contact the REST API and receive said token(s).
|
||||
* This is best done in the {@link authorize()} method itself, by first checking if such a token needs to be
|
||||
* acquired and if so fetch it and save it.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
* @see Request
|
||||
*/
|
||||
interface API_Auth_Interface
|
||||
{
|
||||
/**
|
||||
* Adds authentication details to a given request.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param Request $request The request to modify with authentication details.
|
||||
* @return Request The new request.
|
||||
*/
|
||||
public function authorize(Request $request);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Interfaces;
|
||||
|
||||
use PR\DHL\REST_API\API_Client;
|
||||
use PR\DHL\REST_API\Request;
|
||||
use PR\DHL\REST_API\Response;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An interface for objects that act as REST API drivers.
|
||||
*
|
||||
* A driver is simply a class that knows HOW to send requests to a remote API. Given a full {@link Request} object,
|
||||
* it can transmit it over the network to the remote API and then translate the HTTP response into a {@link Response}
|
||||
* object.
|
||||
*
|
||||
* The purpose of this abstraction is to hide the technicalities of how a request is sent, be it via a low level
|
||||
* library such as cURL, or using a framework's API such as WordPress' functions. API client classes should be the
|
||||
* main consumers of this interface.
|
||||
*
|
||||
* Implementing a new driver involves simply implementing the {@link send()} method. The {@link Request} object that is
|
||||
* received as the argument should contain all the necessary information for making the request, such as the full
|
||||
* URL, params, body, headers and cookies. Once the request is sent and a response is received, a {@link Response}
|
||||
* object should be created, containing the response information, and returned. The created response object should also
|
||||
* have a reference to the original request.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
* @see Request
|
||||
* @see Response
|
||||
* @see API_Client
|
||||
*/
|
||||
interface API_Driver_Interface
|
||||
{
|
||||
/**
|
||||
* Sends a generic request to the REST API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param Request $request The request to send.
|
||||
* @return Response The response.
|
||||
*
|
||||
* @throws RuntimeException If an error occurred and the request could not be sent.
|
||||
*/
|
||||
public function send(Request $request);
|
||||
}
|
||||
153
dev/dhl-for-woocommerce/includes/REST_API/Internetmarke/Auth.php
Normal file
153
dev/dhl-for-woocommerce/includes/REST_API/Internetmarke/Auth.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Internetmarke;
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Request;
|
||||
use PR\DHL\REST_API\URL_Utils;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Authorization handler for Deutsche Post INTERNETMARKE.
|
||||
*/
|
||||
class Auth implements API_Auth_Interface
|
||||
{
|
||||
const AUTH_ROUTE = 'user';
|
||||
|
||||
protected $driver;
|
||||
|
||||
protected $api_url;
|
||||
|
||||
protected $client_id;
|
||||
|
||||
protected $client_secret;
|
||||
|
||||
protected $username;
|
||||
|
||||
protected $password;
|
||||
|
||||
protected $transient;
|
||||
|
||||
protected $token;
|
||||
|
||||
public function __construct(API_Driver_Interface $driver, $api_url, $client_id, $client_secret, $username, $password, $transient)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
$this->api_url = $api_url;
|
||||
$this->client_id = $client_id;
|
||||
$this->client_secret = $client_secret;
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
$this->transient = $transient;
|
||||
|
||||
$this->load_token();
|
||||
}
|
||||
|
||||
public function authorize(Request $request)
|
||||
{
|
||||
if (empty($this->token) || empty($this->token->access_token)) {
|
||||
$this->save_token($this->request_token());
|
||||
}
|
||||
|
||||
$request->headers['Authorization'] = 'Bearer '.$this->token->access_token;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function test_connection()
|
||||
{
|
||||
return $this->request_token();
|
||||
}
|
||||
|
||||
public function request_token()
|
||||
{
|
||||
$full_url = URL_Utils::merge_url_and_route($this->api_url, static::AUTH_ROUTE);
|
||||
|
||||
$body = [
|
||||
'client_id' => $this->client_id,
|
||||
'client_secret' => $this->client_secret,
|
||||
'username' => $this->username,
|
||||
'password' => $this->password,
|
||||
'grant_type' => 'client_credentials',
|
||||
];
|
||||
|
||||
$request = new Request(
|
||||
Request::TYPE_POST,
|
||||
$full_url,
|
||||
[],
|
||||
$body,
|
||||
[
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'Accept' => 'application/json',
|
||||
]
|
||||
);
|
||||
|
||||
$response = $this->driver->send($request);
|
||||
|
||||
if ((int) $response->status !== 200) {
|
||||
$message = $this->extract_error_message($response->body, $response->status);
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$token = is_object($response->body) ? $response->body : json_decode((string) $response->body);
|
||||
|
||||
if (empty($token) || empty($token->access_token)) {
|
||||
throw new RuntimeException(esc_html__('INTERNETMARKE authorization did not return an access token.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function save_token($token)
|
||||
{
|
||||
$this->token = $token;
|
||||
|
||||
if (empty($token)) {
|
||||
delete_transient($this->transient);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$expires_in = ! empty($token->expires_in) ? absint($token->expires_in) : HOUR_IN_SECONDS;
|
||||
set_transient($this->transient, $token, $expires_in);
|
||||
}
|
||||
|
||||
public function delete_token()
|
||||
{
|
||||
$this->token = null;
|
||||
delete_transient($this->transient);
|
||||
}
|
||||
|
||||
protected function load_token()
|
||||
{
|
||||
$this->token = get_transient($this->transient);
|
||||
}
|
||||
|
||||
protected function extract_error_message($body, $status)
|
||||
{
|
||||
if (is_string($body)) {
|
||||
$decoded = json_decode($body);
|
||||
} else {
|
||||
$decoded = $body;
|
||||
}
|
||||
|
||||
if (! empty($decoded->detail)) {
|
||||
return sanitize_text_field($decoded->detail);
|
||||
}
|
||||
|
||||
if (! empty($decoded->message)) {
|
||||
return sanitize_text_field($decoded->message);
|
||||
}
|
||||
|
||||
if ((int) $status === 401) {
|
||||
return esc_html__('Authorization failed. Check the INTERNETMARKE credentials and confirm the business application in Portokasse if this is the first API use.', 'dhl-for-woocommerce');
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %d: HTTP response status code. */
|
||||
esc_html__('INTERNETMARKE authorization failed with HTTP %d.', 'dhl-for-woocommerce'),
|
||||
absint($status)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Internetmarke;
|
||||
|
||||
use Exception;
|
||||
use PR\DHL\REST_API\API_Client;
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
|
||||
/**
|
||||
* API client for Deutsche Post INTERNETMARKE.
|
||||
*/
|
||||
class Client extends API_Client
|
||||
{
|
||||
public function __construct($base_url, API_Driver_Interface $driver, ?API_Auth_Interface $auth = null)
|
||||
{
|
||||
parent::__construct($base_url, $driver, $auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout the shopping cart and retrieve the voucher PDF.
|
||||
*
|
||||
* Endpoint verified against the live API: POST /app/shoppingcart/pdf?directCheckout=true
|
||||
* (the previous /orders path does not exist on this API). The JSON_API_Driver
|
||||
* JSON-encodes the payload and decodes the response.
|
||||
*
|
||||
* @param array $payload ShoppingCartPDFRequest payload (pageFormatId, positions, total).
|
||||
* @return object Decoded CheckoutShoppingCartPDFResponse (link, shoppingCart, walletBalance).
|
||||
*
|
||||
* @throws Exception On non-2xx HTTP status.
|
||||
*/
|
||||
public function create_label(array $payload)
|
||||
{
|
||||
$response = $this->post('app/shoppingcart/pdf?directCheckout=true', $payload);
|
||||
|
||||
$status = (int) $response->status;
|
||||
if ($status !== 200 && $status !== 201) {
|
||||
throw new Exception($this->extract_error_message($response->body, $status, 'checkout'));
|
||||
}
|
||||
|
||||
return $this->decode_body($response->body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an already purchased shopping cart by its shop order ID.
|
||||
*
|
||||
* Documented read-only endpoint: GET /app/shoppingcart/{shopOrderId}. It returns
|
||||
* the same CheckoutShoppingCartPDFResponse as the checkout (including the PDF link)
|
||||
* WITHOUT charging the Portokasse again, so it is the safe way to recover a document
|
||||
* whose original link was lost or has expired after a successful purchase.
|
||||
*
|
||||
* @param string $shop_order_id The shopOrderId sent during checkout.
|
||||
* @return object Decoded CheckoutShoppingCartPDFResponse (link, shoppingCart, walletBallance).
|
||||
*
|
||||
* @throws Exception On non-2xx HTTP status.
|
||||
*/
|
||||
public function get_shopping_cart($shop_order_id)
|
||||
{
|
||||
$response = $this->get('app/shoppingcart/'.rawurlencode((string) $shop_order_id));
|
||||
|
||||
$status = (int) $response->status;
|
||||
if ($status !== 200 && $status !== 201) {
|
||||
throw new Exception($this->extract_error_message($response->body, $status, 'retrieve'));
|
||||
}
|
||||
|
||||
return $this->decode_body($response->body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a response body defensively.
|
||||
*
|
||||
* JSON_API_Driver only decodes when the Content-Type is exactly `application/json`.
|
||||
* The INTERNETMARKE gateway may return `application/json;charset=UTF-8`, which leaves
|
||||
* the body a raw JSON string — decode it here so callers always receive an object.
|
||||
* Mirrors the guard already used by Internetmarke\Auth::request_token().
|
||||
*
|
||||
* @param string|object $body Raw or already-decoded response body.
|
||||
* @return object|null Decoded object, or null when the body is not JSON.
|
||||
*/
|
||||
protected function decode_body($body)
|
||||
{
|
||||
return is_object($body) ? $body : json_decode((string) $body);
|
||||
}
|
||||
|
||||
protected function extract_error_message($body, $status, $operation)
|
||||
{
|
||||
if (is_string($body)) {
|
||||
$decoded = json_decode($body);
|
||||
} else {
|
||||
$decoded = $body;
|
||||
}
|
||||
|
||||
// INTERNETMARKE errors carry a machine-readable `title` plus a `description`.
|
||||
if (! empty($decoded->title)) {
|
||||
$friendly = $this->friendly_error($decoded->title);
|
||||
if ($friendly !== '') {
|
||||
return $friendly;
|
||||
}
|
||||
|
||||
$description = ! empty($decoded->description) ? ' ('.sanitize_text_field($decoded->description).')' : '';
|
||||
|
||||
return sanitize_text_field($decoded->title).$description;
|
||||
}
|
||||
|
||||
if (! empty($decoded->detail)) {
|
||||
return sanitize_text_field($decoded->detail);
|
||||
}
|
||||
|
||||
if (! empty($decoded->message)) {
|
||||
return sanitize_text_field($decoded->message);
|
||||
}
|
||||
|
||||
if ((int) $status === 401) {
|
||||
return esc_html__('Authorization failed. Check the INTERNETMARKE credentials and confirm the business application in Portokasse if this is the first API use.', 'dhl-for-woocommerce');
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: 1: operation name, 2: HTTP status code. */
|
||||
esc_html__('INTERNETMARKE %1$s request failed with HTTP %2$d.', 'dhl-for-woocommerce'),
|
||||
sanitize_text_field($operation),
|
||||
absint($status)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map known INTERNETMARKE error titles to a human-readable message.
|
||||
*
|
||||
* @param string $title The `title` field from the API error body.
|
||||
* @return string A translated message, or '' if the title is unmapped.
|
||||
*/
|
||||
protected function friendly_error($title)
|
||||
{
|
||||
switch ($title) {
|
||||
case 'walletBalanceNotEnough':
|
||||
return esc_html__('Not enough balance in your Portokasse to buy this label. Please top up your Portokasse account and try again.', 'dhl-for-woocommerce');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
93
dev/dhl-for-woocommerce/includes/REST_API/Paket/Auth.php
Normal file
93
dev/dhl-for-woocommerce/includes/REST_API/Paket/Auth.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Paket;
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Request;
|
||||
|
||||
/**
|
||||
* The authorization controller for DHL Paket
|
||||
*
|
||||
* The DHL Packet requires that requests send an Basic HTTP Authorization, ie. "Authorization: Basic 123456" header, where "123456" is an
|
||||
* base64 encoded username:password (or clientID: clientSecret).
|
||||
*
|
||||
* Also, for Pickup 360, a user/customer username and login must be passed in the header "DPDHL user authentication token" base64 encoded username:password
|
||||
*
|
||||
* @since [*next-version*]
|
||||
* @see https://entwickler.dhl.de/en/group/ep/authentifizierung6
|
||||
*/
|
||||
class Auth implements API_Auth_Interface
|
||||
{
|
||||
/**
|
||||
* The driver to use for obtaining and revoking the access token.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* The client's ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $client_id;
|
||||
|
||||
/**
|
||||
* The authentication secret for the client.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $client_secret;
|
||||
|
||||
/**
|
||||
* API key provided by DHL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $api_key;
|
||||
|
||||
/**
|
||||
* The REST API base URL.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $api_url;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param API_Driver_Interface $driver The driver to use for obtaining and revoking the access token.
|
||||
* @param string $api_url The REST API base URL.
|
||||
* @param string $client_id The client's ID.
|
||||
* @param string $client_secret The authentication secret for the client.
|
||||
* @param string $api_key The API key provided by DHL.
|
||||
*/
|
||||
public function __construct(API_Driver_Interface $driver, $api_url, $client_id, $client_secret, $api_key)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
$this->api_url = $api_url;
|
||||
$this->client_id = $client_id;
|
||||
$this->client_secret = $client_secret;
|
||||
$this->api_key = $api_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function authorize(Request $request)
|
||||
{
|
||||
$request->headers['Authorization'] = 'Basic '.base64_encode($this->client_id.':'.$this->client_secret);
|
||||
$request->headers['dhl-api-key'] = $this->api_key;
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
257
dev/dhl-for-woocommerce/includes/REST_API/Paket/Client.php
Normal file
257
dev/dhl-for-woocommerce/includes/REST_API/Paket/Client.php
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
<?php
|
||||
|
||||
namespace PR\DHL\REST_API\Paket;
|
||||
|
||||
use Exception;
|
||||
use PR\DHL\REST_API\API_Client;
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
|
||||
/**
|
||||
* The API client for DHL Paket.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
class Client extends API_Client
|
||||
{
|
||||
/**
|
||||
* The DHL Customer Portal Username
|
||||
*/
|
||||
protected $customer_portal_user;
|
||||
|
||||
/**
|
||||
* The DHL Customer Portal Password
|
||||
*/
|
||||
protected $customer_portal_password;
|
||||
|
||||
/**
|
||||
* The language of the message
|
||||
*/
|
||||
protected $language = 'en';
|
||||
|
||||
/**
|
||||
* The version of the message
|
||||
*/
|
||||
protected $version = '1';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $customer_portal_user, $customer_portal_password The customer username and password
|
||||
*/
|
||||
public function __construct($customer_portal_user, $customer_portal_password, $base_url, API_Driver_Interface $driver, ?API_Auth_Interface $auth = null)
|
||||
{
|
||||
parent::__construct($base_url, $driver, $auth);
|
||||
|
||||
$this->customer_portal_user = $customer_portal_user;
|
||||
$this->customer_portal_password = $customer_portal_password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pickup_request
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param class $pickup_request_info Pickup_Request_Info
|
||||
*/
|
||||
public function create_pickup_request(Pickup_Request_Info $pickup_request_info)
|
||||
{
|
||||
$route = $this->request_pickup_route();
|
||||
|
||||
// Customer business portal user auth
|
||||
$headers = [
|
||||
'Authorization' => 'Basic '.base64_encode($this->customer_portal_user.':'.$this->customer_portal_password),
|
||||
'dhl-api-key' => defined('PR_DHL_GLOBAL_API') ? PR_DHL_GLOBAL_API : '',
|
||||
];
|
||||
|
||||
$data = $this->request_pickup_info_to_request_data($pickup_request_info);
|
||||
$response = $this->post($route, $data, $headers);
|
||||
|
||||
if ($response->status === 200) {
|
||||
$response->body = json_decode($response->body);
|
||||
if (isset($response->body->confirmation->value->orderID)) {
|
||||
return $response->body;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
wp_kses_post(
|
||||
sprintf(
|
||||
// Translators: %s is replaced with the error details returned from the API.
|
||||
__('Failed DHL Request Pickup1: %s', 'dhl-for-woocommerce'),
|
||||
$this->generate_error_details($response->body)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create get pickup locations request
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function get_pickup_location($postalCode = '')
|
||||
{
|
||||
|
||||
$route = $this->get_pickup_location_route();
|
||||
$data = ['postalCode' => $postalCode];
|
||||
$response = $this->get($route, $data);
|
||||
|
||||
if ($response->status === 200) {
|
||||
return $response->body;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
wp_kses_post(
|
||||
sprintf(
|
||||
// Translators: %s is replaced with the error details returned from the API.
|
||||
__('Failed DHL Request Pickup: %s', 'dhl-for-woocommerce'),
|
||||
$this->generate_error_details($response->body)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function generate_error_details($body)
|
||||
{
|
||||
$error_details = '';
|
||||
|
||||
if (isset($body->title)) {
|
||||
$error_details = $body->title;
|
||||
} elseif (is_string($body)) {
|
||||
$error_details = $body;
|
||||
} elseif (is_array($body)) {
|
||||
$error_details = '<br><ol>';
|
||||
foreach ($body as $error) {
|
||||
$error_details .= '<li>'.$error->title.'</li>';
|
||||
}
|
||||
$error_details .= '</ol>';
|
||||
}
|
||||
|
||||
return $error_details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date time.
|
||||
*
|
||||
* @return string The date and time of the message.
|
||||
*/
|
||||
protected function get_datetime()
|
||||
{
|
||||
return gmdate('c', time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message language.
|
||||
*
|
||||
* @return string The language of the message.
|
||||
*/
|
||||
protected function get_language()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message version.
|
||||
*
|
||||
* @return string The version of the message.
|
||||
*/
|
||||
protected function get_version()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an item info object into a request data array.
|
||||
*
|
||||
* @param Pickup_Request_Info $request_pickup_info The item info object to transform.
|
||||
* @return array The request data for the given item info object.
|
||||
*/
|
||||
protected function request_pickup_info_to_request_data(Pickup_Request_Info $request_pickup_info)
|
||||
{
|
||||
// Pickup date
|
||||
if ($request_pickup_info->pickup_details['dhl_pickup_type'] === 'date') {
|
||||
$pickup_date = [
|
||||
'type' => 'Date',
|
||||
'value' => $request_pickup_info->pickup_details['dhl_pickup_date'],
|
||||
];
|
||||
} else {
|
||||
$pickup_date = [
|
||||
'type' => 'ASAP',
|
||||
];
|
||||
}
|
||||
|
||||
// Pickup location & business hours
|
||||
$pickup_location_array = [
|
||||
'type' => 'Address',
|
||||
'pickupAddress' => [
|
||||
'name1' => $request_pickup_info->pickup_contact['name'],
|
||||
'name2' => '',
|
||||
'addressStreet' => $request_pickup_info->pickup_address['addressStreet'],
|
||||
'addressHouse' => $request_pickup_info->pickup_address['addressHouse'],
|
||||
'city' => $request_pickup_info->pickup_address['city'],
|
||||
'postalCode' => $request_pickup_info->pickup_address['postalCode'],
|
||||
'state' => $request_pickup_info->pickup_address['state'],
|
||||
'country' => $request_pickup_info->pickup_address['country'],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($request_pickup_info->shipments as $key => $shipment) {
|
||||
if ($shipment['transportationType'] === 'SPERRGUT') {
|
||||
$request_pickup_info->shipments[$key]['pickupServices']['bulkyGood'] = [
|
||||
'comment' => 'Bulky Goods',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'customerDetails' => [
|
||||
'billingNumber' => $request_pickup_info->customer_details['billingNumber'],
|
||||
],
|
||||
'pickupLocation' => $pickup_location_array,
|
||||
'pickupDetails' => [
|
||||
'pickupDate' => $pickup_date,
|
||||
],
|
||||
'shipmentDetails' => [
|
||||
'shipments' => $request_pickup_info->shipments,
|
||||
],
|
||||
'contactPerson' => [
|
||||
[
|
||||
'name' => $request_pickup_info->pickup_contact['name'],
|
||||
'phone' => $request_pickup_info->pickup_contact['phone'],
|
||||
'email' => $request_pickup_info->pickup_contact['email'],
|
||||
'emailNotification' => [
|
||||
'sendPickupConfirmationEmail' => 'true',
|
||||
'sendPickupTimeWindowEmail' => 'true',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an API route with the customer namespace and EKP.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function request_pickup_route()
|
||||
{
|
||||
return '/orders';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an API route with the customer namespace and EKP.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_pickup_location_route()
|
||||
{
|
||||
return '/locations';
|
||||
}
|
||||
}
|
||||
1683
dev/dhl-for-woocommerce/includes/abstract-pr-dhl-wc-order.php
Normal file
1683
dev/dhl-for-woocommerce/includes/abstract-pr-dhl-wc-order.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PR_DHL_WC_Product_Editor class file.
|
||||
*/
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
|
||||
if (! class_exists('PR_DHL_WC_Product_Editor')) {
|
||||
/**
|
||||
* PR_DHL_WC_Product_Editor Class.
|
||||
*/
|
||||
abstract class PR_DHL_WC_Product_Editor
|
||||
{
|
||||
/**
|
||||
* Manufacture country tooltip.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $manufacture_tooltip = '';
|
||||
|
||||
/**
|
||||
* Manufacture country label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $manufacture_country_label = '';
|
||||
|
||||
/**
|
||||
* HS Code label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hs_code_label = '';
|
||||
|
||||
/**
|
||||
* HS Code description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hs_code_description = '';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
add_action('woocommerce_block_template_area_product-form_after_add_block_product-shipping-dimensions', [$this, 'add_product_shipping_fields']);
|
||||
add_action('woocommerce_block_template_area_product-form_after_add_block_product-variation-shipping-dimensions', [$this, 'add_product_shipping_fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional product fields.
|
||||
*/
|
||||
abstract public function additional_product_fields($parent): void;
|
||||
|
||||
/**
|
||||
* Add DHL product fields.
|
||||
*/
|
||||
public function add_product_shipping_fields(BlockInterface $shipping_dimensions_block): void
|
||||
{
|
||||
if (! method_exists($shipping_dimensions_block, 'get_parent')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = $shipping_dimensions_block->get_parent();
|
||||
|
||||
// Add Country of Origin Select Block.
|
||||
$countries = WC()->countries->get_countries();
|
||||
$parent->add_block(
|
||||
[
|
||||
'id' => '_dhl_manufacture_country',
|
||||
'blockName' => 'woocommerce/product-select-field',
|
||||
'attributes' => [
|
||||
'label' => $this->manufacture_country_label,
|
||||
'property' => 'meta_data._dhl_manufacture_country',
|
||||
'options' => array_merge(
|
||||
[
|
||||
[
|
||||
'value' => '0',
|
||||
'label' => __('- select country -', 'dhl-for-woocommerce'),
|
||||
],
|
||||
],
|
||||
array_map(function ($key, $value) {
|
||||
return [
|
||||
'value' => $key,
|
||||
'label' => $value,
|
||||
];
|
||||
}, array_keys($countries), $countries)
|
||||
),
|
||||
'tooltip' => $this->manufacture_tooltip,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// Add HS Tariff Code Text Block.
|
||||
$parent->add_block(
|
||||
[
|
||||
'id' => '_dhl_hs_code',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'attributes' => [
|
||||
'label' => $this->hs_code_label,
|
||||
'property' => 'meta_data._dhl_hs_code',
|
||||
'placeholder' => esc_html__('HS Code', 'dhl-for-woocommerce'),
|
||||
'tooltip' => $this->hs_code_description,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->additional_product_fields($parent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
195
dev/dhl-for-woocommerce/includes/abstract-pr-dhl-wc-product.php
Normal file
195
dev/dhl-for-woocommerce/includes/abstract-pr-dhl-wc-product.php
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce DHL Shipping Order.
|
||||
*
|
||||
* @category Product
|
||||
*
|
||||
* @author Shadi Manna
|
||||
*/
|
||||
if (! class_exists('PR_DHL_WC_Product')) {
|
||||
|
||||
abstract class PR_DHL_WC_Product
|
||||
{
|
||||
protected $manufacture_country_label = '';
|
||||
|
||||
protected $hs_code_label = '';
|
||||
|
||||
protected $hs_code_description = '';
|
||||
|
||||
/**
|
||||
* Init and hook in the integration.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this->manufacture_country_label = esc_html__('Country of Manufacture (DHL)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_label = esc_html__('Harmonized Tariff Schedule (DHL)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_description = esc_html__('Harmonized Tariff Schedule is a number assigned to every possible commodity that can be imported or exported from any country.', 'dhl-for-woocommerce');
|
||||
|
||||
// priority is '8' because WC Subscriptions hides fields in the shipping tabs which hide the DHL fields here
|
||||
add_action('woocommerce_product_options_shipping', [$this, 'additional_product_shipping_options'], 8);
|
||||
add_action('woocommerce_process_product_meta', [$this, 'save_additional_product_shipping_options']);
|
||||
add_action('woocommerce_product_bulk_edit_end', [$this, 'product_shipping_bulk_edit_input']);
|
||||
add_action('woocommerce_product_bulk_edit_save', [$this, 'save_product_shipping_bulk_edit']);
|
||||
add_action('woocommerce_product_quick_edit_end', [$this, 'product_shipping_bulk_edit_input']);
|
||||
add_action('woocommerce_product_quick_edit_save', [$this, 'save_product_shipping_bulk_edit']);
|
||||
add_action('manage_product_posts_custom_column', [$this, 'product_shipping_hidden_input_value'], 20, 2);
|
||||
add_action('admin_enqueue_scripts', [$this, 'product_enqueue_scripts'], 20, 1);
|
||||
}
|
||||
|
||||
public function product_enqueue_scripts($hook)
|
||||
{
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ($screen->id == 'edit-product') {
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-shipment-dhl-product-list-js',
|
||||
PR_DHL_PLUGIN_DIR_URL.'/assets/js/pr-dhl-product-list.js',
|
||||
['jquery'],
|
||||
PR_DHL_VERSION
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the meta box for shipment info on the order page
|
||||
*/
|
||||
public function additional_product_shipping_options()
|
||||
{
|
||||
global $thepostid, $post;
|
||||
|
||||
$thepostid = empty($thepostid) ? $post->ID : $thepostid;
|
||||
|
||||
// $countries_obj = new WC_Countries();
|
||||
// $countries = $countries_obj->esc_html__get('countries');
|
||||
$countries = WC()->countries->get_countries();
|
||||
|
||||
$manufacture_tip = $this->get_manufacture_tooltip();
|
||||
$countries = array_merge(['0' => esc_html__('- select country -', 'dhl-for-woocommerce')], $countries);
|
||||
|
||||
woocommerce_wp_select(
|
||||
[
|
||||
'id' => '_dhl_manufacture_country',
|
||||
'label' => $this->manufacture_country_label,
|
||||
'description' => $manufacture_tip,
|
||||
'desc_tip' => 'true',
|
||||
/* 'value' => $country_value, */
|
||||
'options' => $countries,
|
||||
]
|
||||
);
|
||||
|
||||
woocommerce_wp_text_input(
|
||||
[
|
||||
'id' => '_dhl_hs_code',
|
||||
'label' => $this->hs_code_label,
|
||||
'description' => $this->hs_code_description,
|
||||
'desc_tip' => 'true',
|
||||
'placeholder' => esc_html__('HS Code', 'dhl-for-woocommerce'),
|
||||
]
|
||||
);
|
||||
|
||||
echo '<p class="description" style="margin: -8px 12px 12px;">'.wp_kses_post(
|
||||
sprintf(
|
||||
/* translators: %s is a link to the US HTSUS lookup tool */
|
||||
__('For shipments to the United States, enter the full 10-digit code — %s.', 'dhl-for-woocommerce'),
|
||||
'<a href="https://hts.usitc.gov/" target="_blank" rel="noopener noreferrer">'.esc_html__('look up the HTSUS code', 'dhl-for-woocommerce').'</a>'
|
||||
)
|
||||
).'</p>';
|
||||
|
||||
$this->additional_product_settings();
|
||||
}
|
||||
|
||||
public function product_shipping_bulk_edit_input()
|
||||
{
|
||||
|
||||
$countries = WC()->countries->get_countries();
|
||||
$countries = array_merge(['0' => esc_html__('- No change -', 'dhl-for-woocommerce')], $countries);
|
||||
?>
|
||||
<div class="inline-edit-group dhl_manufacture_country_inline">
|
||||
<label class="alignleft">
|
||||
<span class="title"><?php echo esc_html__('Country of Manufacture (DHL)', 'dhl-for-woocommerce'); ?></span>
|
||||
<span class="input-text-wrap">
|
||||
<select class="change_dhl_manufacture_country change_to" name="change_dhl_manufacture_country">
|
||||
<?php foreach ($countries as $value => $text) { ?>
|
||||
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_attr($text); ?></option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="inline-edit-group dhl_hs_code_inline">
|
||||
<label class="alignleft">
|
||||
<span class="title"><?php echo esc_html__('Harmonized Tariff Schedule (DHL)', 'dhl-for-woocommerce'); ?></span>
|
||||
<span class="input-text-wrap">
|
||||
<input type="text" name="change_dhl_hs_code" class="change_dhl_hs_code text" value="" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
abstract public function get_manufacture_tooltip();
|
||||
|
||||
abstract public function additional_product_settings();
|
||||
|
||||
public function save_additional_product_shipping_options($post_id)
|
||||
{
|
||||
|
||||
// Country of manufacture
|
||||
if (isset($_POST['_dhl_manufacture_country'])) {
|
||||
update_post_meta($post_id, '_dhl_manufacture_country', wc_clean($_POST['_dhl_manufacture_country']));
|
||||
}
|
||||
|
||||
// HS code value
|
||||
if (isset($_POST['_dhl_hs_code'])) {
|
||||
update_post_meta($post_id, '_dhl_hs_code', wc_clean($_POST['_dhl_hs_code']));
|
||||
}
|
||||
|
||||
$this->save_additional_product_settings($post_id);
|
||||
}
|
||||
|
||||
public function save_product_shipping_bulk_edit($product)
|
||||
{
|
||||
$post_id = $product->get_id();
|
||||
if (! empty($_REQUEST['change_dhl_hs_code'])) {
|
||||
update_post_meta($post_id, '_dhl_hs_code', wc_clean($_REQUEST['change_dhl_hs_code']));
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['change_dhl_manufacture_country']) && $_REQUEST['change_dhl_manufacture_country'] != '0') {
|
||||
update_post_meta($post_id, '_dhl_manufacture_country', wc_clean($_REQUEST['change_dhl_manufacture_country']));
|
||||
}
|
||||
}
|
||||
|
||||
public function product_shipping_hidden_input_value($column, $post_id)
|
||||
{
|
||||
switch ($column) {
|
||||
case 'name':
|
||||
?>
|
||||
<div class="hidden dhl_hs_code_inline" id="dhl_hs_code_inline_<?php echo esc_attr($post_id); ?>">
|
||||
<div id="dhl_hs_code"><?php echo esc_html(get_post_meta($post_id, '_dhl_hs_code', true)); ?></div>
|
||||
</div>
|
||||
<div class="hidden dhl_manufacture_country_inline" id="dhl_manufacture_country_inline_<?php echo esc_attr($post_id); ?>">
|
||||
<div id="dhl_manufacture_country"><?php echo esc_html(get_post_meta($post_id, '_dhl_manufacture_country', true)); ?></div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function save_additional_product_settings($post_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
import { useEffect, useState, useCallback } from '@wordpress/element';
|
||||
import { Notice } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CART_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { debounce } from 'lodash';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
export const Block = ({ checkoutExtensionData }) => {
|
||||
const { setExtensionData } = checkoutExtensionData;
|
||||
|
||||
// Access the localized data from prDhlGlobals
|
||||
const imgUrl = prDhlGlobals?.pluginUrl + "/assets/img/dhl-official.png";
|
||||
const dhlSettings = prDhlGlobals.dhlSettings;
|
||||
|
||||
const [error, setError] = useState( null );
|
||||
const [closestDP, setClosestDP] = useState('no');
|
||||
const [displayClosest, setDisplayClosest] = useState( true );
|
||||
|
||||
const closestAvailable = dhlSettings?.closest_drop_point;
|
||||
const validCountries = prDhlGlobals?.valid_countries || [];
|
||||
|
||||
|
||||
// Retrieve customer data
|
||||
const customerData = useSelect( ( select ) => select( CART_STORE_KEY ).getCustomerData(), [] );
|
||||
const shippingAddress = customerData ? customerData.shippingAddress : null;
|
||||
|
||||
// Debounce for reducing the number of updates to the extension data
|
||||
const debouncedSetExtensionData = useCallback( debounce( ( namespace, key, value ) => {
|
||||
setExtensionData( namespace, key, value );
|
||||
}, 500 ), [setExtensionData] );
|
||||
|
||||
const cart = useSelect(
|
||||
(select) => select(CART_STORE_KEY).getCartData(),
|
||||
[]
|
||||
) || {};
|
||||
|
||||
const cartNeedsShipping = cart.needsShipping === true;
|
||||
|
||||
useEffect( () => {
|
||||
if (
|
||||
! shippingAddress ||
|
||||
! closestAvailable ||
|
||||
! validCountries.includes( shippingAddress.country )
|
||||
) {
|
||||
setDisplayClosest( false );
|
||||
return;
|
||||
} else {
|
||||
setDisplayClosest( true );
|
||||
return;
|
||||
}
|
||||
|
||||
}, [ shippingAddress, closestAvailable ] );
|
||||
|
||||
// useEffect for closestDP
|
||||
useEffect( () => {
|
||||
setExtensionData( 'pr-dhl', 'closest_drop_point', closestDP );
|
||||
debouncedSetExtensionData( 'pr-dhl', 'closest_drop_point', closestDP );
|
||||
}, [ closestDP ] );
|
||||
|
||||
if ( cartNeedsShipping === false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! displayClosest ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( error ) {
|
||||
return (
|
||||
<Notice status="error" isDismissible={false}>
|
||||
{__( error, 'dhl-for-woocommerce' )}
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
|
||||
// Render DHL logo dynamically from the localized data
|
||||
return (
|
||||
<table className="dhl-co-table">
|
||||
<tbody>
|
||||
{/* DHL logo */}
|
||||
<tr className="dhl-co-tr dhl-co-tr-first">
|
||||
<td colSpan={2}>
|
||||
<img src={imgUrl} alt="DHL logo" className="dhl-co-logo" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Title and description */}
|
||||
<tr className="dhl-co-tr">
|
||||
<th colSpan={2}>
|
||||
{ __( 'Closest drop-off point', 'dhl-for-woocommerce' ) }
|
||||
<hr />
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr className="dhl-co-tr">
|
||||
<td colSpan={2}>
|
||||
{ __( 'Preferred delivery to a parcel shop/parcel locker close to the specified home address', 'dhl-for-woocommerce' ) }
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr className="dhl-co-tr">
|
||||
<th className="dhl-cdp">
|
||||
{ __( 'Delivery option', 'dhl-for-woocommerce' ) }
|
||||
</th>
|
||||
<td className="dhl-cdp">
|
||||
<ul className="dhl-preferred-location">
|
||||
<li>
|
||||
<input
|
||||
defaultChecked={true}
|
||||
type="radio"
|
||||
name="pr_dhl_cdp_delivery"
|
||||
data-index={0} id="dhl_home_deliver_option"
|
||||
value="no"
|
||||
className=""
|
||||
onChange={(e) => setClosestDP(e.target.value)} />
|
||||
<label htmlFor="dhl_home_deliver_option">{ __( 'Home delivery', 'dhl-for-woocommerce' ) }</label>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type="radio"
|
||||
name="pr_dhl_cdp_delivery"
|
||||
data-index={0} id="dhl_cdp_option"
|
||||
value="yes"
|
||||
className=""
|
||||
onChange={(e) => setClosestDP(e.target.value)} />
|
||||
<label htmlFor="dhl_cdp_option">{ __( 'Closest Drop Point', 'dhl-for-woocommerce' ) }</label>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="dhl-co-tr dhl-co-tr-last">
|
||||
<td colSpan={2}></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"apiVersion": 3,
|
||||
"name": "pr-dhl/dhl-closest-drop-point",
|
||||
"version": "1.0.0",
|
||||
"title": "DHL Order Block",
|
||||
"category": "woocommerce",
|
||||
"description": "Adds DHL block to checkout.",
|
||||
"supports": {
|
||||
"html": false,
|
||||
"align": true,
|
||||
"multiple": false,
|
||||
"reusable": false
|
||||
},
|
||||
"parent": [
|
||||
"woocommerce/checkout-order-summary-block"
|
||||
],
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"mainTitle": {
|
||||
"type": "string",
|
||||
"default": "Closest drop-off point."
|
||||
},
|
||||
"infoText": {
|
||||
"type": "string",
|
||||
"default": "Preferred delivery to a parcel shop/parcel locker close to the specified home address."
|
||||
},
|
||||
"homeDeliveryLabel": {
|
||||
"type": "string",
|
||||
"default": "Home delivery"
|
||||
},
|
||||
"closestDropPointLabel": {
|
||||
"type": "string",
|
||||
"default": "Closest Drop Point"
|
||||
}
|
||||
},
|
||||
"textdomain": "dhl-for-woocommerce",
|
||||
"editorStyle": "file:../../../build/style-dhl-closest-drop-point.css"
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {__} from '@wordpress/i18n';
|
||||
import {
|
||||
useBlockProps,
|
||||
RichText,
|
||||
InspectorControls,
|
||||
} from '@wordpress/block-editor';
|
||||
import {PanelBody} from '@wordpress/components';
|
||||
import {getSetting} from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
|
||||
export const Edit = ({attributes, setAttributes}) => {
|
||||
return (null);
|
||||
|
||||
};
|
||||
|
||||
export const Save = ({attributes}) => {
|
||||
return (null);
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerCheckoutBlock } from '@woocommerce/blocks-checkout';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Block } from './block';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerCheckoutBlock({
|
||||
metadata,
|
||||
component: Block,
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, box } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType(metadata, {
|
||||
icon: {
|
||||
src: <Icon icon={box} />,
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
});
|
||||
|
|
@ -0,0 +1,500 @@
|
|||
import {useState, useEffect, useCallback} from '@wordpress/element';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import {Button, TextControl, SelectControl} from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import axios from 'axios';
|
||||
import {debounce} from "lodash";
|
||||
|
||||
// Droppoint address types come from PHP — PR_DHL()->get_droppoint_address_types() —
|
||||
// exposed via prDhlGlobals, so the set has a single source of truth shared with the server.
|
||||
const isDroppointAddressType = ( type ) =>
|
||||
( prDhlGlobals?.droppointAddressTypes || [] ).includes( type );
|
||||
|
||||
export const Block = ({checkoutExtensionData}) => {
|
||||
|
||||
const { setExtensionData } = checkoutExtensionData;
|
||||
|
||||
// Debounce for reducing the number of updates to the extension data
|
||||
const debouncedSetExtensionData = useCallback(debounce((namespace, key, value) => {
|
||||
setExtensionData(namespace, key, value);
|
||||
}, 500), [setExtensionData]);
|
||||
|
||||
const [isPageLoaded, setIsPageLoaded] = useState(false);
|
||||
const [parcelShops, setParcelShops] = useState([]);
|
||||
const [dropOffPoint, setDropOffPoint] = useState('');
|
||||
|
||||
// Retrieve shipping calculation & rates state
|
||||
const { hasCalculatedShipping, shippingRates } = useSelect((select) => {
|
||||
const cartStore = select('wc/store/cart');
|
||||
return {
|
||||
hasCalculatedShipping: cartStore.getHasCalculatedShipping(),
|
||||
shippingRates: cartStore.getShippingRates(),
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Retrieve & update shipping address
|
||||
const { setShippingAddress } = useDispatch('wc/store/cart');
|
||||
const shippingAddress = useSelect((select) =>
|
||||
select('wc/store/cart').getCustomerData()?.shippingAddress || {},
|
||||
[]);
|
||||
|
||||
// Determine address type
|
||||
const [addressType, setAddressType] = useState('');
|
||||
const [postNumber, setPostNumber] = useState('');
|
||||
|
||||
// Derive booleans for each filter.
|
||||
const isPackstation = addressType === 'dhl_packstation';
|
||||
const isBranch = addressType === 'dhl_branch';
|
||||
|
||||
// Handle page load (for when map button should display)
|
||||
useEffect(() => {
|
||||
const handlePageLoad = () => setIsPageLoaded(true);
|
||||
if (document.readyState === 'complete') {
|
||||
setIsPageLoaded(true);
|
||||
} else {
|
||||
window.addEventListener('load', handlePageLoad);
|
||||
return () => window.removeEventListener('load', handlePageLoad);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const showMapButton = hasCalculatedShipping &&
|
||||
shippingRates.length > 0 &&
|
||||
isPageLoaded && (shippingAddress.country === 'DE') &&
|
||||
prDhlGlobals.dhlSettings.display_google_maps;
|
||||
|
||||
// Fetch parcel shops when shippingAddress changes and is available
|
||||
useEffect(() => {
|
||||
if (!shippingAddress || !shippingAddress.country || !shippingAddress.postcode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('action', 'wc_shipment_dhl_parcelfinder_search');
|
||||
formData.append('parcelfinder_country', shippingAddress.country);
|
||||
formData.append('parcelfinder_postcode', shippingAddress.postcode);
|
||||
formData.append('parcelfinder_city', shippingAddress.city || '');
|
||||
formData.append('parcelfinder_address', shippingAddress.address_1 || '');
|
||||
formData.append('packstation_filter', isPackstation ? 'true' : 'false');
|
||||
formData.append('branch_filter', isBranch ? 'true' : 'false');
|
||||
formData.append('security', prDhlGlobals.parcel_nonce);
|
||||
|
||||
axios.post(prDhlGlobals.ajax_url, formData.toString(), {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.data.error && response.data.parcel_res) {
|
||||
setParcelShops(response.data.parcel_res);
|
||||
} else {
|
||||
setParcelShops([]);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setParcelShops([]);
|
||||
});
|
||||
}, [shippingAddress, isPackstation, isBranch]);
|
||||
|
||||
// Update shipping address when user selects a drop-off point
|
||||
useEffect(() => {
|
||||
if (!dropOffPoint || parcelShops.length === 0) return;
|
||||
const selectedShop = parcelShops.find(shop => shop.location.ids[0].locationId === dropOffPoint);
|
||||
if (selectedShop) {
|
||||
let shop_name = '';
|
||||
let address_type = '';
|
||||
|
||||
switch (selectedShop.location.type) {
|
||||
case 'locker':
|
||||
shop_name = __('Packstation', 'dhl-for-woocommerce');
|
||||
address_type = 'dhl_packstation';
|
||||
break;
|
||||
case 'servicepoint':
|
||||
shop_name = __('Postfiliale', 'dhl-for-woocommerce');
|
||||
address_type = 'dhl_branch';
|
||||
break;
|
||||
case 'postoffice':
|
||||
case 'postbank':
|
||||
shop_name = __('Postfiliale', 'dhl-for-woocommerce');
|
||||
address_type = 'dhl_branch';
|
||||
break;
|
||||
default:
|
||||
shop_name = __('Packstation', 'dhl-for-woocommerce');
|
||||
address_type = 'dhl_packstation';
|
||||
}
|
||||
|
||||
const newShippingAddress = {
|
||||
...shippingAddress,
|
||||
address_1: `${shop_name} ${selectedShop.location.keywordId}`,
|
||||
address_2: '',
|
||||
postcode: selectedShop.place.address.postalCode,
|
||||
city: selectedShop.place.address.addressLocality,
|
||||
};
|
||||
|
||||
setShippingAddress(newShippingAddress);
|
||||
setAddressType(address_type);
|
||||
}
|
||||
}, [dropOffPoint]);
|
||||
useEffect(() => {
|
||||
const handleShopSelected = (event) => {
|
||||
|
||||
const { address_1, address_2, postcode, city ,parcel_ID} = event.detail;
|
||||
const newShippingAddress = {
|
||||
...shippingAddress,
|
||||
address_1,
|
||||
address_2,
|
||||
postcode,
|
||||
city,
|
||||
};
|
||||
setShippingAddress(newShippingAddress);
|
||||
setDropOffPoint(parcel_ID);
|
||||
setAddressType(address_type);
|
||||
|
||||
};
|
||||
|
||||
window.addEventListener('dhl-shop-selected', handleShopSelected);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('dhl-shop-selected', handleShopSelected);
|
||||
};
|
||||
}, [setShippingAddress, shippingAddress]);
|
||||
|
||||
// Determine the correct registration link based on locale.
|
||||
const registrationLink = prDhlGlobals.locale === 'en_US'
|
||||
? prDhlGlobals.DHL_ENGLISH_REGISTRATION_LINK
|
||||
: prDhlGlobals.DHL_GERMAN_REGISTRATION_LINK;
|
||||
|
||||
const validationErrorId = 'dhl-post-number-validation';
|
||||
|
||||
//clearing validation errors.
|
||||
const { setValidationErrors, clearValidationError } = useDispatch( 'wc/store/validation' );
|
||||
|
||||
// Retrieve the current validation error.
|
||||
const validationError = useSelect( ( select ) => {
|
||||
return select( 'wc/store/validation' ).getValidationError( validationErrorId );
|
||||
}, [ validationErrorId ] );
|
||||
|
||||
useEffect(() => {
|
||||
setExtensionData('pr-dhl', 'addressType', addressType);
|
||||
debouncedSetExtensionData('pr-dhl', 'addressType', addressType);
|
||||
}, [addressType]);
|
||||
|
||||
useEffect(() => {
|
||||
setExtensionData('pr-dhl', 'postNumber', postNumber);
|
||||
debouncedSetExtensionData('pr-dhl', 'postNumber', postNumber);
|
||||
}, [postNumber]);
|
||||
|
||||
/**
|
||||
* PostNumber Validation.
|
||||
*/
|
||||
|
||||
useEffect(() => {
|
||||
clearValidationError(validationErrorId);
|
||||
|
||||
const address1 = shippingAddress.address_1 ? shippingAddress.address_1 : '';
|
||||
const addressLower = address1.toLowerCase();
|
||||
const PACKSTATION_RE = /\bpackstation\s+\d{3}\b/i;
|
||||
const POSTFILIALE_RE = /\bpostfiliale\s+\d{3,5}\b/i;
|
||||
|
||||
const pos_ps = PACKSTATION_RE.test(addressLower);
|
||||
const pos_rs = POSTFILIALE_RE.test(addressLower);
|
||||
const pos_po = POSTFILIALE_RE.test(addressLower);
|
||||
|
||||
if ( addressType === 'dhl_packstation' ) {
|
||||
|
||||
if ( ! postNumber.trim() ) {
|
||||
setValidationErrors({
|
||||
[validationErrorId]: {
|
||||
message: __( 'Post Number is mandatory for a Packstation location.', 'dhl-for-woocommerce' ),
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! pos_ps ) {
|
||||
setValidationErrors({
|
||||
[validationErrorId]: {
|
||||
message: __( 'The text "Packstation" must be included in the address.', 'dhl-for-woocommerce' ),
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
} else if ( addressType === 'dhl_branch' ) {
|
||||
if ( ! pos_rs ) {
|
||||
setValidationErrors({
|
||||
[validationErrorId]: {
|
||||
message: __( 'The text "Postfiliale" must be included in the address.', 'dhl-for-woocommerce' ),
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if ( ! pos_po ) {
|
||||
setValidationErrors({
|
||||
[validationErrorId]: {
|
||||
message: __( 'The text "Postfiliale" must be included in the address.', 'dhl-for-woocommerce' ),
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Post Number only applies to Packstation / Postfiliale, not a Regular Address.
|
||||
if ( postNumber && isDroppointAddressType( addressType ) ) {
|
||||
if ( isNaN( parseInt( postNumber, 10 ) ) ) {
|
||||
setValidationErrors({
|
||||
[validationErrorId]: {
|
||||
message: __( 'Post Number must be a number.', 'dhl-for-woocommerce' ),
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ( postNumber.length < 6 || postNumber.length > 12 ) {
|
||||
setValidationErrors({
|
||||
[validationErrorId]: {
|
||||
message: __( 'The post number you entered is not valid. Please correct the number.', 'dhl-for-woocommerce' ),
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}, [
|
||||
addressType,
|
||||
postNumber,
|
||||
shippingAddress,
|
||||
clearValidationError,
|
||||
setValidationErrors,
|
||||
validationErrorId,
|
||||
]);
|
||||
|
||||
const addressTypeOptions = [
|
||||
{label: __('Address Type', 'dhl-for-woocommerce'), value: ''},
|
||||
{label: __('Regular Address', 'dhl-for-woocommerce'), value: 'normal'},
|
||||
];
|
||||
if (prDhlGlobals.dhlSettings.display_packstation) {
|
||||
addressTypeOptions.push({
|
||||
label: __('DHL Packstation', 'dhl-for-woocommerce'),
|
||||
value: 'dhl_packstation',
|
||||
});
|
||||
}
|
||||
|
||||
if (prDhlGlobals.dhlSettings.display_post_office) {
|
||||
addressTypeOptions.push({
|
||||
label: __('DHL Branch', 'dhl-for-woocommerce'),
|
||||
value: 'dhl_branch',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
{showMapButton && (prDhlGlobals.dhlSettings.display_post_office || prDhlGlobals.dhlSettings.display_parcelshop || prDhlGlobals.dhlSettings.display_packstation ) && (
|
||||
<>
|
||||
|
||||
{/* Registration info displayed above the shipping fields */}
|
||||
<div className="registration_info">
|
||||
{__('For deliveries to DHL Parcel Lockers you have to', 'dhl-for-woocommerce')}{' '}
|
||||
<a href={registrationLink} target="_blank" rel="noopener noreferrer">
|
||||
{__('create a DHL account', 'dhl-for-woocommerce')}
|
||||
</a>{' '}
|
||||
{__('and get a Post Number.', 'dhl-for-woocommerce')}
|
||||
</div>
|
||||
<Button
|
||||
isPrimary
|
||||
id="dhl_parcel_finder"
|
||||
data-fancybox
|
||||
data-src="#dhl_parcel_finder_form"
|
||||
data-options='{"touch":false,"clickSlide":false,"dragToClose":false}'
|
||||
href="javascript:;"
|
||||
onClick={() => {
|
||||
// Reinitialize jQuery bindings, for example:
|
||||
if (typeof wc_checkout_dhl_parcelfinder !== 'undefined') {
|
||||
wc_checkout_dhl_parcelfinder.init();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{__('Search Packstation / Branch', 'dhl-for-woocommerce')}
|
||||
<img
|
||||
src={`${prDhlGlobals.pluginUrl}/assets/img/dhl-official.png`}
|
||||
alt="DHL logo"
|
||||
className="dhl-co-logo"
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<div style={{display: 'none'}}>
|
||||
<div id="dhl_parcel_finder_form">
|
||||
<form id="checkout_dhl_parcel_finder" method="post">
|
||||
<p className="form-row form-field small">
|
||||
<input
|
||||
type="text"
|
||||
name="dhl_parcelfinder_postcode"
|
||||
className="input-text"
|
||||
placeholder={__('Post Code', 'dhl-for-woocommerce')}
|
||||
id="dhl_parcelfinder_postcode"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p className="form-row form-field small">
|
||||
<input
|
||||
type="text"
|
||||
name="dhl_parcelfinder_city"
|
||||
className="input-text"
|
||||
placeholder={__('City', 'dhl-for-woocommerce')}
|
||||
id="dhl_parcelfinder_city"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p className="form-row form-field large">
|
||||
<input
|
||||
type="text"
|
||||
name="dhl_parcelfinder_address"
|
||||
className="input-text"
|
||||
placeholder={__('Address', 'dhl-for-woocommerce')}
|
||||
id="dhl_parcelfinder_address"
|
||||
/>
|
||||
</p>
|
||||
|
||||
{prDhlGlobals.dhlSettings.display_packstation && (
|
||||
<p className="form-row form-field packstation">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="dhl_packstation_filter"
|
||||
className="input-checkbox"
|
||||
id="dhl_packstation_filter"
|
||||
value="1"
|
||||
defaultChecked
|
||||
/>
|
||||
<label htmlFor="dhl_packstation_filter">
|
||||
{__('Packstation', 'dhl-for-woocommerce')}
|
||||
</label>
|
||||
<span
|
||||
className="icon"
|
||||
style={{backgroundImage: `url(${prDhlGlobals.pluginUrl}/assets/img/packstation.png)`}}
|
||||
></span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{(prDhlGlobals.dhlSettings.display_parcelshop || prDhlGlobals.dhlSettings.display_post_office) && (
|
||||
<p className="form-row form-field parcelshop">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="dhl_branch_filter"
|
||||
className="input-checkbox"
|
||||
id="dhl_branch_filter"
|
||||
value="1"
|
||||
defaultChecked
|
||||
/>
|
||||
<label htmlFor="dhl_branch_filter">
|
||||
{__('Branch', 'dhl-for-woocommerce')}
|
||||
</label>
|
||||
<span className="parcel-wrap">
|
||||
{prDhlGlobals.dhlSettings.display_parcelshop && (
|
||||
<span
|
||||
className="icon"
|
||||
style={{backgroundImage: `url(${prDhlGlobals.pluginUrl}/assets/img/parcelshop.png)`}}
|
||||
></span>
|
||||
)}
|
||||
{prDhlGlobals.dhlSettings.display_post_office && (
|
||||
<span
|
||||
className="icon"
|
||||
style={{backgroundImage: `url(${prDhlGlobals.pluginUrl}/assets/img/post_office.png)`}}
|
||||
></span>
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p id="dhl_seach_button" className="form-row form-field small">
|
||||
<input
|
||||
type="submit"
|
||||
className="button"
|
||||
name="apply_parcel_finder"
|
||||
value={__('Search', 'dhl-for-woocommerce')}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<input type="hidden" name="dhl_parcelfinder_country" id="dhl_parcelfinder_country"/>
|
||||
<input type="hidden" name="dhl_parcelfinder_nonce" value={prDhlGlobals.parcel_nonce}/>
|
||||
|
||||
<div className="clear"></div>
|
||||
|
||||
{/* Close Button */}
|
||||
<button data-fancybox-close className="fancybox-close-small" title="close">
|
||||
<svg viewBox="0 0 32 32">
|
||||
<path d="M10,10 L22,22 M22,10 L10,22"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div id="dhl_google_map"></div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{ validationError && ! validationError.hidden && (
|
||||
<div className="wc-block-components-validation-error">
|
||||
{ validationError.message }
|
||||
</div>
|
||||
) }
|
||||
|
||||
{/* Address Type */}
|
||||
{((prDhlGlobals.dhlSettings.display_post_office || prDhlGlobals.dhlSettings.display_parcelshop || prDhlGlobals.dhlSettings.display_packstation)) && shippingAddress.country === 'DE' && (
|
||||
<>
|
||||
<SelectControl
|
||||
className="wc-blocks-components-select__select"
|
||||
value={addressType}
|
||||
id="shipping_dhl_address_type"
|
||||
onChange={(value) => {
|
||||
setAddressType(value);
|
||||
// Regular Address carries no Post Number; drop any stale value.
|
||||
if (value === 'normal' || value === '') {
|
||||
setPostNumber('');
|
||||
}
|
||||
}}
|
||||
options={addressTypeOptions}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Drop off point */}
|
||||
{showMapButton && addressType !== 'normal' && (prDhlGlobals.dhlSettings.display_post_office || prDhlGlobals.dhlSettings.display_packstation ) &&(
|
||||
<>
|
||||
<div className="wc-blocks-components-select__select">
|
||||
<SelectControl
|
||||
value={dropOffPoint}
|
||||
onChange={(value) => setDropOffPoint(value)}
|
||||
options={[
|
||||
{label: __('Select a drop off points', 'dhl-for-woocommerce'), value: ''},
|
||||
...parcelShops.map((shop) => ({
|
||||
label: shop.name,
|
||||
value: shop.location.ids[0].locationId,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* Post Number */}
|
||||
{addressType !== 'normal' && (prDhlGlobals.dhlSettings.display_post_office || prDhlGlobals.dhlSettings.display_packstation ) &&(
|
||||
<>
|
||||
<div className="wc-block-components-text-input ">
|
||||
<TextControl
|
||||
placeholder={ __('Post Number', 'dhl-for-woocommerce') }
|
||||
value={ postNumber }
|
||||
className="wc-block-components-text-input"
|
||||
onChange={ ( val ) => setPostNumber(val) }
|
||||
required={ addressType === 'dhl_packstation' }
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"apiVersion": 3,
|
||||
"name": "pr-dhl/dhl-parcel-finder",
|
||||
"version": "1.0.0",
|
||||
"title": "DHL Order Block",
|
||||
"category": "woocommerce",
|
||||
"description": "Adds DHL block to checkout.",
|
||||
"supports": {
|
||||
"html": false,
|
||||
"align": true,
|
||||
"multiple": false,
|
||||
"reusable": false
|
||||
},
|
||||
"parent": [
|
||||
"woocommerce/checkout-contact-information-block"
|
||||
],
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"addressType": {
|
||||
"type": "object",
|
||||
"default": ""
|
||||
},
|
||||
"dhlPostnum": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"pfPostcode": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"pfCity": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"pfAddress": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"textdomain": "dhl-for-woocommerce",
|
||||
"editorStyle": "file:../../../build/style-dhl-preferred-services.css"
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {__} from '@wordpress/i18n';
|
||||
import {
|
||||
useBlockProps,
|
||||
RichText,
|
||||
InspectorControls,
|
||||
} from '@wordpress/block-editor';
|
||||
import {PanelBody, SelectControl, RadioControl, TextControl, Disabled} from '@wordpress/components';
|
||||
import {getSetting} from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
|
||||
export const Edit = ({attributes, setAttributes}) => {
|
||||
return (null);
|
||||
|
||||
};
|
||||
|
||||
export const Save = ({attributes}) => {
|
||||
return (null);
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerCheckoutBlock } from '@woocommerce/blocks-checkout';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Block } from './block';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerCheckoutBlock({
|
||||
metadata,
|
||||
component: Block,
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, box } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType(metadata, {
|
||||
icon: {
|
||||
src: <Icon icon={box} />,
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
});
|
||||
|
|
@ -0,0 +1,475 @@
|
|||
import { useEffect, useState, useCallback } from '@wordpress/element';
|
||||
import { TextControl, RadioControl, Spinner, Notice } from '@wordpress/components';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { CART_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { debounce } from 'lodash';
|
||||
import axios from 'axios';
|
||||
|
||||
export const Block = ({ checkoutExtensionData }) => {
|
||||
const { setExtensionData } = checkoutExtensionData;
|
||||
|
||||
// Access the localized data from prDhlGlobals
|
||||
const imgUrl = prDhlGlobals.pluginUrl + "/assets/img/dhl-official.png";
|
||||
const dhlSettings = prDhlGlobals.dhlSettings;
|
||||
const [displayPreferred, setDisplayPreferred] = useState(true);
|
||||
|
||||
const { updateCustomerData } = useDispatch(CART_STORE_KEY);
|
||||
|
||||
// Debounce for reducing the number of updates to the extension data
|
||||
const debouncedSetExtensionData = useCallback(debounce((namespace, key, value) => {
|
||||
setExtensionData(namespace, key, value);
|
||||
}, 500), [setExtensionData]);
|
||||
|
||||
const debounceTimer = useState(null);
|
||||
|
||||
const customerData = useSelect((select) => select(CART_STORE_KEY).getCustomerData(), []);
|
||||
const shippingAddress = customerData ? customerData.shippingAddress : null;
|
||||
|
||||
const address1Lower = (shippingAddress?.address_1 || '').toLowerCase();
|
||||
|
||||
const PACKSTATION_RE = /\bpackstation\s+\d{3}\b/i;
|
||||
const POSTFILIALE_RE = /\bpostfiliale\s+\d{3,5}\b/i;
|
||||
|
||||
const selectedIsPackstation = PACKSTATION_RE.test(address1Lower);
|
||||
const selectedIsBranch = POSTFILIALE_RE.test(address1Lower);
|
||||
const isLockerOrBranch = selectedIsPackstation || selectedIsBranch;
|
||||
|
||||
useEffect(() => {
|
||||
if (isLockerOrBranch) {
|
||||
setPreferredLocationNeighbor('none');
|
||||
setPreferredLocation('');
|
||||
setPreferredNeighborName('');
|
||||
setPreferredNeighborAddress('');
|
||||
}
|
||||
}, [isLockerOrBranch]);
|
||||
|
||||
// Determine availability of location and neighbor options
|
||||
const locationAvailable = dhlSettings?.preferred_location;
|
||||
const neighborAvailable = dhlSettings?.preferred_neighbour;
|
||||
const allowLocationNeighborUI = !isLockerOrBranch;
|
||||
const showRadioControl = allowLocationNeighborUI && locationAvailable && neighborAvailable;
|
||||
|
||||
// Initialize preferredLocationNeighbor
|
||||
const initialPreferredLocationNeighbor = showRadioControl ? 'none' : locationAvailable ? 'location' : neighborAvailable ? 'neighbor' : 'none';
|
||||
|
||||
// State hooks for the block fields
|
||||
const [preferredDay, setPreferredDay] = useState('');
|
||||
const [preferredDays, setPreferredDays] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [preferredLocationNeighbor, setPreferredLocationNeighbor] = useState(initialPreferredLocationNeighbor);
|
||||
const [preferredLocation, setPreferredLocation] = useState('');
|
||||
const [preferredNeighborName, setPreferredNeighborName] = useState('');
|
||||
const [preferredNeighborAddress, setPreferredNeighborAddress] = useState('');
|
||||
|
||||
|
||||
// Retrieve selected shipping methods and payment method
|
||||
const cartData = useSelect((select) => select(CART_STORE_KEY).getCartData(), []);
|
||||
const selectedShippingMethods = cartData ? cartData.selectedShippingMethods : [];
|
||||
const selectedPaymentMethod = cartData ? cartData.selectedPaymentMethod : '';
|
||||
|
||||
// State to keep track of whether the preferred day fee has been applied
|
||||
const [preferredDayFeeApplied, setPreferredDayFeeApplied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
// Clear previous timer on re-render.
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
// Check if shippingAddress is valid
|
||||
if (
|
||||
!shippingAddress ||
|
||||
shippingAddress.country !== 'DE' ||
|
||||
!shippingAddress.city ||
|
||||
!shippingAddress.postcode
|
||||
) {
|
||||
setDisplayPreferred(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce the request by 1.5 seconds.
|
||||
debounceTimer.current = setTimeout(() => {
|
||||
if (shippingAddress) {
|
||||
const data = {
|
||||
shipping_country: shippingAddress.country || '',
|
||||
shipping_postcode: shippingAddress.postcode || '',
|
||||
shipping_address_1: shippingAddress.address_1 || '',
|
||||
shipping_address_2: shippingAddress.address_2 || '',
|
||||
shipping_city: shippingAddress.city || '',
|
||||
shipping_state: shippingAddress.state || '',
|
||||
shipping_email: shippingAddress.email || '',
|
||||
shipping_phone: shippingAddress.phone || '',
|
||||
shipping_company: shippingAddress.company || '',
|
||||
shipping_methods: selectedShippingMethods,
|
||||
payment_method: selectedPaymentMethod,
|
||||
};
|
||||
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('action', 'pr_dhl_set_checkout_post_data');
|
||||
formData.append('nonce', prDhlGlobals.nonce);
|
||||
|
||||
// Append each data field
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (Array.isArray(data[key])) {
|
||||
data[key].forEach((item, index) => {
|
||||
formData.append(`data[${key}][${index}]`, item);
|
||||
});
|
||||
} else {
|
||||
formData.append(`data[${key}]`, data[key]);
|
||||
}
|
||||
});
|
||||
|
||||
// Send the data via AJAX
|
||||
axios.post(prDhlGlobals.ajax_url, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
// Fetch the preferredDays
|
||||
fetchPreferredDays();
|
||||
} else {
|
||||
setDisplayPreferred(false);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setDisplayPreferred(false);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
}, 750);
|
||||
// clear timer if user keeps typing.
|
||||
return () => clearTimeout(debounceTimer.current);
|
||||
|
||||
}, [shippingAddress, selectedShippingMethods, selectedPaymentMethod]);
|
||||
|
||||
const fetchPreferredDays = () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('action', 'pr_dhl_get_preferred_days');
|
||||
formData.append('nonce', prDhlGlobals.nonce);
|
||||
|
||||
axios.post(prDhlGlobals.ajax_url, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
|
||||
if (response.data.success) {
|
||||
|
||||
setPreferredDays(response.data.data.preferredDays);
|
||||
setDisplayPreferred(true);
|
||||
setLoading(false);
|
||||
} else {
|
||||
setDisplayPreferred(false);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setDisplayPreferred(false);
|
||||
setError('Error fetching preferred days.');
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
// useEffect for preferredDay
|
||||
useEffect(() => {
|
||||
setExtensionData('pr-dhl', 'preferredDay', preferredDay);
|
||||
debouncedSetExtensionData('pr-dhl', 'preferredDay', preferredDay);
|
||||
|
||||
// Update customer data to refresh the checkout
|
||||
updateCustomerData();
|
||||
|
||||
// Handle adding/removing the preferred day fee
|
||||
const preferredDayCost = parseFloat(dhlSettings?.preferred_day_cost || 0);
|
||||
|
||||
// Function to update the cart fee via Store API
|
||||
const updateCartFee = async (feeAmount, feeLabel) => {
|
||||
try {
|
||||
const { extensionCartUpdate } = window.wc.blocksCheckout || {};
|
||||
|
||||
if (typeof extensionCartUpdate === 'function') {
|
||||
await extensionCartUpdate({
|
||||
namespace: 'pr-dhl',
|
||||
data: {
|
||||
action: 'update_preferred_day_fee',
|
||||
price: feeAmount,
|
||||
label: feeLabel,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating cart fee:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (preferredDay && preferredDay !== '0' && preferredDayCost > 0) {
|
||||
if (!preferredDayFeeApplied) {
|
||||
// Add the fee
|
||||
updateCartFee(preferredDayCost, __('DHL Delivery Day', 'dhl-for-woocommerce'));
|
||||
setPreferredDayFeeApplied(true);
|
||||
}
|
||||
} else {
|
||||
if (preferredDayFeeApplied) {
|
||||
// Remove the fee
|
||||
updateCartFee(0, '');
|
||||
setPreferredDayFeeApplied(false);
|
||||
}
|
||||
}
|
||||
}, [preferredDay]);
|
||||
|
||||
// useEffect for preferredLocationNeighbor
|
||||
useEffect(() => {
|
||||
setExtensionData('pr-dhl', 'preferredLocationNeighbor', preferredLocationNeighbor);
|
||||
debouncedSetExtensionData('pr-dhl', 'preferredLocationNeighbor', preferredLocationNeighbor);
|
||||
}, [preferredLocationNeighbor]);
|
||||
|
||||
// useEffect for preferredLocation
|
||||
useEffect(() => {
|
||||
setExtensionData('pr-dhl', 'preferredLocation', preferredLocation);
|
||||
debouncedSetExtensionData('pr-dhl', 'preferredLocation', preferredLocation);
|
||||
}, [preferredLocation]);
|
||||
|
||||
// useEffect for preferredNeighborName
|
||||
useEffect(() => {
|
||||
setExtensionData('pr-dhl', 'preferredNeighborName', preferredNeighborName);
|
||||
debouncedSetExtensionData('pr-dhl', 'preferredNeighborName', preferredNeighborName);
|
||||
}, [preferredNeighborName]);
|
||||
|
||||
// useEffect for preferredNeighborAddress
|
||||
useEffect(() => {
|
||||
setExtensionData('pr-dhl', 'preferredNeighborAddress', preferredNeighborAddress);
|
||||
debouncedSetExtensionData('pr-dhl', 'preferredNeighborAddress', preferredNeighborAddress);
|
||||
}, [preferredNeighborAddress]);
|
||||
|
||||
// Handle visibility of drop-off location and neighbor fields based on settings and selection
|
||||
const showDropOffLocation = allowLocationNeighborUI && ((showRadioControl && preferredLocationNeighbor === 'location') || (!showRadioControl && locationAvailable));
|
||||
const showNeighborFields = allowLocationNeighborUI && ((showRadioControl && preferredLocationNeighbor === 'neighbor') || (!showRadioControl && neighborAvailable));
|
||||
|
||||
// Update the mapping of preferredDayOptions
|
||||
let preferredDayOptions = [];
|
||||
if (preferredDays && Object.keys(preferredDays).length > 0) {
|
||||
preferredDayOptions = Object.entries(preferredDays).map(([key, dayName]) => {
|
||||
let weekDayNum = '';
|
||||
if (key === '0' || key === 'none') {
|
||||
weekDayNum = '-';
|
||||
} else {
|
||||
const date = new Date(key);
|
||||
if (isNaN(date.getTime())) {
|
||||
weekDayNum = '-';
|
||||
} else {
|
||||
weekDayNum = date.getDate().toString();
|
||||
}
|
||||
}
|
||||
return {
|
||||
weekDayNum,
|
||||
dayName,
|
||||
key,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (!displayPreferred) {
|
||||
return null; // Or display a message indicating that preferred services are not available
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Notice status="error" isDismissible={false}>
|
||||
{__(error, 'dhl-for-woocommerce')}
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
|
||||
if ( shippingAddress && shippingAddress.country !== 'DE' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Render DHL logo dynamically from the localized data
|
||||
return (<table className="dhl-co-table">
|
||||
{/* DHL logo */}
|
||||
<tr className="dhl-co-tr dhl-co-tr-first">
|
||||
<td colSpan="2">
|
||||
<img src={imgUrl} alt="DHL logo" className="dhl-co-logo" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Title and description */}
|
||||
<tr className="dhl-co-tr">
|
||||
<th colSpan="2">
|
||||
{__('DHL Preferred Delivery. Delivered just as you wish.', 'dhl-for-woocommerce')}
|
||||
<hr />
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr className="dhl-co-tr">
|
||||
<td colSpan="2">
|
||||
{__('Thanks to the flexible recipient services of DHL Preferred Delivery, you decide when and where you want to receive your parcels. Please choose your preferred delivery option.', 'dhl-for-woocommerce')}
|
||||
</td>
|
||||
</tr>
|
||||
{/* Preferred Delivery Day */}
|
||||
{dhlSettings?.preferred_day && (<>
|
||||
<tr className="dhl-co-tr">
|
||||
<th colSpan="2" className="dhl-pt">
|
||||
{__('Delivery day: Delivery at your preferred day', 'dhl-for-woocommerce')}
|
||||
<span
|
||||
className="dhl-tooltip"
|
||||
title={__('Choose one of the displayed days as your preferred day for your parcel delivery. Other days are not possible due to delivery processes.', 'dhl-for-woocommerce')}
|
||||
>
|
||||
?
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="dhl-co-tr">
|
||||
<td colSpan="2">
|
||||
{dhlSettings?.preferred_day_cost && parseFloat(dhlSettings.preferred_day_cost) > 0 ? (
|
||||
<>
|
||||
{sprintf(
|
||||
__('There is a surcharge of %s incl. VAT for this service.*', 'dhl-for-woocommerce'),
|
||||
wcPrice(parseFloat(dhlSettings.preferred_day_cost))
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
__('There is a surcharge for this service.', 'dhl-for-woocommerce')
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="dhl-co-tr">
|
||||
<td colSpan="2">
|
||||
{preferredDayOptions.length > 0 ? (
|
||||
<ul className="dhl-co-times">
|
||||
{preferredDayOptions.map((option, index) => (
|
||||
<li key={index}>
|
||||
<input
|
||||
type="radio"
|
||||
name="pr_dhl_preferred_day"
|
||||
className="pr_dhl_preferred_day"
|
||||
data-index="0"
|
||||
id={`pr_dhl_preferred_day_${option.key}`}
|
||||
value={option.key}
|
||||
checked={preferredDay === option.key}
|
||||
onChange={(e) => setPreferredDay(e.target.value)}
|
||||
/>
|
||||
<label htmlFor={`pr_dhl_preferred_day_${option.key}`}>
|
||||
{option.weekDayNum}<br />{option.dayName}
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<i>
|
||||
{__('Unfortunately, for the selected delivery address the service Delivery Day is not available', 'dhl-for-woocommerce')}
|
||||
</i>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</>)}
|
||||
|
||||
{/* Drop-off location or neighbor */}
|
||||
{showRadioControl && (<>
|
||||
<tr className="dhl-co-tr">
|
||||
<th className="dhl-pt">
|
||||
{__('Drop-off location or neighbor', 'dhl-for-woocommerce')}
|
||||
</th>
|
||||
<td className="dhl-pt">
|
||||
<RadioControl
|
||||
selected={preferredLocationNeighbor}
|
||||
options={[
|
||||
{ label: __('None', 'dhl-for-woocommerce'), value: 'none' },
|
||||
{ label: __('Location', 'dhl-for-woocommerce'), value: 'location' },
|
||||
{ label: __('Neighbor', 'dhl-for-woocommerce'), value: 'neighbor' },
|
||||
]}
|
||||
onChange={(value) => setPreferredLocationNeighbor(value)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</>)}
|
||||
|
||||
{/* Preferred Drop-off Location */}
|
||||
{showDropOffLocation && (<>
|
||||
<tr className="dhl-co-tr">
|
||||
<th colSpan="2" className="dhl-pt">
|
||||
{__('Drop-off location: Delivery to your preferred drop-off location', 'dhl-for-woocommerce')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="dhl-co-tr">
|
||||
<td colSpan="2">
|
||||
<div className="wc-block-components-text-input">
|
||||
<TextControl
|
||||
placeholder={__('e.g. Garage, Terrace', 'dhl-for-woocommerce')}
|
||||
value={preferredLocation}
|
||||
onChange={(value) => setPreferredLocation(value)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>)}
|
||||
|
||||
{/* Preferred Neighbor */}
|
||||
{showNeighborFields && (<>
|
||||
<tr className="dhl-co-tr">
|
||||
<th colSpan="2" className="dhl-pt">
|
||||
{__('Neighbour: Delivery to a neighbour of your choice', 'dhl-for-woocommerce')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="dhl-co-tr">
|
||||
<td colSpan="2">
|
||||
<div className="wc-block-components-text-input">
|
||||
<TextControl
|
||||
className={'pr-dhl-other-textarea'}
|
||||
placeholder={__('First name, last name of neighbour', 'dhl-for-woocommerce')}
|
||||
value={preferredNeighborName}
|
||||
onChange={(value) => setPreferredNeighborName(value)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="dhl-co-tr">
|
||||
<td colSpan="2" className="">
|
||||
<div className="wc-block-components-text-input">
|
||||
<TextControl
|
||||
placeholder={__('Street, number, postal code, city', 'dhl-for-woocommerce')}
|
||||
value={preferredNeighborAddress}
|
||||
onChange={(value) => setPreferredNeighborAddress(value)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>)}
|
||||
</table>);
|
||||
};
|
||||
|
||||
// Helper function to format price
|
||||
function wcPrice(amount) {
|
||||
// Assuming you have access to WooCommerce currency settings
|
||||
const currencySymbol = prDhlGlobals.currencySymbol || '€';
|
||||
const currencyPosition = prDhlGlobals.currencyPosition || 'left';
|
||||
const decimals = prDhlGlobals.currencyDecimals || 2;
|
||||
const decimalSeparator = prDhlGlobals.currencyDecimalSeparator || '.';
|
||||
const thousandSeparator = prDhlGlobals.currencyThousandSeparator || ',';
|
||||
|
||||
amount = parseFloat(amount).toFixed(decimals);
|
||||
|
||||
const parts = amount.toString().split('.');
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
|
||||
const formattedAmount = parts.join(decimalSeparator);
|
||||
|
||||
if (currencyPosition === 'left') {
|
||||
return currencySymbol + formattedAmount;
|
||||
} else {
|
||||
return formattedAmount + currencySymbol;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"apiVersion": 3,
|
||||
"name": "pr-dhl/dhl-preferred-services",
|
||||
"version": "1.0.0",
|
||||
"title": "DHL Order Block",
|
||||
"category": "woocommerce",
|
||||
"description": "Adds DHL block to checkout.",
|
||||
"supports": {
|
||||
"html": false,
|
||||
"align": true,
|
||||
"multiple": false,
|
||||
"reusable": false
|
||||
},
|
||||
"parent": [
|
||||
"woocommerce/checkout-order-summary-block"
|
||||
],
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"preferredDay": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"preferredLocationNeighbor": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"preferredLocation": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"preferredNeighborName": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"preferredNeighborAddress": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"mainTitle": {
|
||||
"type": "string",
|
||||
"default": "DHL Preferred Delivery. Delivered just as you wish."
|
||||
},
|
||||
"infoText": {
|
||||
"type": "string",
|
||||
"default": "Thanks to the flexible recipient services of DHL Preferred Delivery, you decide when and where you want to receive your parcels. Please choose your preferred delivery option."
|
||||
},
|
||||
"deliveryDayLabel": {
|
||||
"type": "string",
|
||||
"default": "Delivery day: Delivery at your preferred day ?"
|
||||
},
|
||||
"dropOffOrNeighborLabel": {
|
||||
"type": "string",
|
||||
"default": "Drop-off location or neighbor"
|
||||
},
|
||||
"dropOffLocationLabel": {
|
||||
"type": "string",
|
||||
"default": "Drop-off location: Delivery to your preferred drop-off location"
|
||||
},
|
||||
"neighborLabel": {
|
||||
"type": "string",
|
||||
"default": "Neighbour: Delivery to a neighbour of your choice"
|
||||
}
|
||||
},
|
||||
"textdomain": "dhl-for-woocommerce",
|
||||
"editorStyle": "file:../../../build/style-dhl-preferred-services.css"
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {__} from '@wordpress/i18n';
|
||||
import {
|
||||
useBlockProps,
|
||||
RichText,
|
||||
InspectorControls,
|
||||
} from '@wordpress/block-editor';
|
||||
import {PanelBody, SelectControl, RadioControl, TextControl, Disabled} from '@wordpress/components';
|
||||
import {getSetting} from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const {defaultLabelText} = getSetting('pr-dhl_data', '');
|
||||
|
||||
export const Edit = ({attributes, setAttributes}) => {
|
||||
const {
|
||||
mainTitle,
|
||||
infoText,
|
||||
deliveryDayLabel,
|
||||
dropOffOrNeighborLabel,
|
||||
dropOffLocationLabel,
|
||||
neighborLabel,
|
||||
} = attributes;
|
||||
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
return (
|
||||
<div {...blockProps} style={{display: 'block'}}>
|
||||
<InspectorControls>
|
||||
<PanelBody title={__('DHL Block Options', 'dhl-for-woocommerce')}>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
|
||||
<div>
|
||||
<RichText
|
||||
value={
|
||||
mainTitle ||
|
||||
defaultLabelText ||
|
||||
__('DHL Preferred Delivery. Delivered just as you wish.', 'dhl-for-woocommerce')
|
||||
}
|
||||
onChange={(value) => setAttributes({mainTitle: value})}
|
||||
/>
|
||||
<RichText
|
||||
value={
|
||||
infoText ||
|
||||
defaultLabelText ||
|
||||
__('Thanks to the flexible recipient services of DHL Preferred Delivery, you decide when and where you want to receive your parcels.', 'dhl-for-woocommerce')
|
||||
}
|
||||
onChange={(value) => setAttributes({infoText: value})}
|
||||
/>
|
||||
<RichText
|
||||
value={
|
||||
deliveryDayLabel ||
|
||||
defaultLabelText ||
|
||||
__('Delivery day: Delivery at your preferred day ', 'dhl-for-woocommerce')
|
||||
}
|
||||
onChange={(value) => setAttributes({deliveryDayLabel: value})}
|
||||
/>
|
||||
|
||||
<Disabled>
|
||||
<SelectControl
|
||||
label={deliveryDayLabel}
|
||||
options={[]}
|
||||
/>
|
||||
</Disabled>
|
||||
<RichText
|
||||
value={
|
||||
dropOffOrNeighborLabel ||
|
||||
defaultLabelText ||
|
||||
__('Drop-off location or neighbor', 'dhl-for-woocommerce')
|
||||
}
|
||||
onChange={(value) => setAttributes({dropOffOrNeighborLabel: value})}
|
||||
/>
|
||||
<Disabled>
|
||||
<RadioControl
|
||||
label={dropOffOrNeighborLabel}
|
||||
options={[
|
||||
{label: 'None', value: 'none'},
|
||||
{label: 'Location', value: 'location'},
|
||||
{label: 'Neighbor', value: 'neighbor'},
|
||||
]}
|
||||
/>
|
||||
</Disabled>
|
||||
<RichText
|
||||
value={
|
||||
dropOffLocationLabel ||
|
||||
defaultLabelText ||
|
||||
__('Drop-off location: Delivery to your preferred drop-off location ', 'dhl-for-woocommerce')
|
||||
}
|
||||
onChange={(value) => setAttributes({dropOffLocationLabel: value})}
|
||||
/>
|
||||
<Disabled>
|
||||
<TextControl
|
||||
placholder={__('e.g. Garage, Terrace', 'dhl-for-woocommerce')}
|
||||
/>
|
||||
</Disabled>
|
||||
<RichText
|
||||
value={
|
||||
neighborLabel ||
|
||||
defaultLabelText ||
|
||||
__('Neighbour: Delivery to a neighbour of your choice ', 'dhl-for-woocommerce')
|
||||
}
|
||||
onChange={(value) => setAttributes({neighborLabel: value})}
|
||||
/>
|
||||
<Disabled>
|
||||
<TextControl
|
||||
placholder={__('First name, last name of neighbour', 'dhl-for-woocommerce')}
|
||||
/>
|
||||
</Disabled>
|
||||
<Disabled>
|
||||
<TextControl
|
||||
placholder={__('Street, number, postal code, city', 'dhl-for-woocommerce')}
|
||||
/>
|
||||
</Disabled>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = ({attributes}) => {
|
||||
return (null);
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerCheckoutBlock } from '@woocommerce/blocks-checkout';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Block } from './block';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerCheckoutBlock({
|
||||
metadata,
|
||||
component: Block,
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, box } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType(metadata, {
|
||||
icon: {
|
||||
src: <Icon icon={box} />,
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
});
|
||||
16
dev/dhl-for-woocommerce/includes/checkout-blocks/index.js
Normal file
16
dev/dhl-for-woocommerce/includes/checkout-blocks/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
|
||||
// Import block definitions
|
||||
import './dhl-preferred-services';
|
||||
import './dhl-parcel-finder';
|
||||
import './dhl-closest-drop-point';
|
||||
|
||||
const render = () => {};
|
||||
|
||||
registerPlugin('pr-dhl', {
|
||||
render,
|
||||
scope: 'woocommerce-checkout',
|
||||
});
|
||||
81
dev/dhl-for-woocommerce/includes/class-pr-dhl-autoloader.php
Normal file
81
dev/dhl-for-woocommerce/includes/class-pr-dhl-autoloader.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class PR_DHL_Autoloader
|
||||
{
|
||||
/**
|
||||
* Path to the includes directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $include_path = '';
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
spl_autoload_register([$this, 'autoload']);
|
||||
|
||||
$this->include_path = untrailingslashit(plugin_dir_path(PR_DHL_PLUGIN_FILE)).'/includes/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a class name and turn it into a file name.
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
private function get_file_name_from_class($class)
|
||||
{
|
||||
return 'class-'.str_replace('_', '-', $class).'.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Include a class file.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool successful or not
|
||||
*/
|
||||
private function load_file($path)
|
||||
{
|
||||
if ($path && is_readable($path)) {
|
||||
include_once $path;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-load WC classes on demand to reduce memory consumption.
|
||||
*
|
||||
* @param string $class
|
||||
*/
|
||||
public function autoload($class)
|
||||
{
|
||||
$class = strtolower($class);
|
||||
$file = $this->get_file_name_from_class($class);
|
||||
$path = '';
|
||||
|
||||
if (strpos($class, 'pr_dhl_api') === 0) {
|
||||
$path = $this->include_path.'pr-dhl-api/';
|
||||
}
|
||||
|
||||
if (strpos($class, 'front_end') !== false) {
|
||||
$path = $this->include_path.'front-end/';
|
||||
}
|
||||
|
||||
// If the path is empty -> not DHL API class, or class couldn't load, try parent dir load
|
||||
if (empty($path) || ! $this->load_file($path.$file)) {
|
||||
$this->load_file($this->include_path.$file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new PR_DHL_Autoloader;
|
||||
|
|
@ -0,0 +1,433 @@
|
|||
<?php
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for integrating with WooCommerce Blocks.
|
||||
*
|
||||
* @category Shipping
|
||||
*/
|
||||
class PR_DHL_Blocks_Integration implements IntegrationInterface
|
||||
{
|
||||
/**
|
||||
* The name of the integration.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name()
|
||||
{
|
||||
return 'pr-dhl';
|
||||
}
|
||||
|
||||
/**
|
||||
* When called, invokes any initialization/setup for the integration.
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$this->register_scripts_and_styles();
|
||||
$this->register_ajax_actions();
|
||||
$this->localize_scripts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers scripts and styles for both editor and frontend.
|
||||
*/
|
||||
private function register_scripts_and_styles()
|
||||
{
|
||||
// Register main integration script and style
|
||||
$this->register_main_integration();
|
||||
|
||||
// Register block editor scripts
|
||||
$this->register_block_script(
|
||||
'pr-dhl-preferred-services-editor',
|
||||
'/build/pr-dhl-preferred-services.js',
|
||||
'/build/pr-dhl-preferred-services.asset.php'
|
||||
);
|
||||
|
||||
// Register frontend scripts for blocks
|
||||
$this->register_frontend_script(
|
||||
'pr-dhl-preferred-services-frontend',
|
||||
'/build/pr-dhl-preferred-services-frontend.js',
|
||||
'/build/pr-dhl-preferred-services-frontend.asset.php'
|
||||
);
|
||||
|
||||
// Register block editor scripts
|
||||
$this->register_block_script(
|
||||
'pr-dhl-closest-drop-point-editor',
|
||||
'/build/pr-dhl-closest-drop-point.js',
|
||||
'/build/pr-dhl-closest-drop-point.asset.php'
|
||||
);
|
||||
|
||||
// Register frontend scripts for blocks
|
||||
$this->register_frontend_script(
|
||||
'pr-dhl-closest-drop-point-frontend',
|
||||
'/build/pr-dhl-closest-drop-point-frontend.js',
|
||||
'/build/pr-dhl-closest-drop-point-frontend.asset.php'
|
||||
);
|
||||
|
||||
// Register parcel finder editor scripts
|
||||
$this->register_block_script(
|
||||
'pr-dhl-parcel-finder-editor',
|
||||
'/build/pr-dhl-parcel-finder.js',
|
||||
'/build/pr-dhl-parcel-finder.asset.php'
|
||||
);
|
||||
|
||||
// Register frontend scripts for parcel finder
|
||||
$this->register_frontend_script(
|
||||
'pr-dhl-parcel-finder-frontend',
|
||||
'/build/pr-dhl-parcel-finder-frontend.js',
|
||||
'/build/pr-dhl-parcel-finder-frontend.asset.php'
|
||||
);
|
||||
|
||||
// Register block styles
|
||||
$this->register_styles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the main JS and CSS files required for the integration.
|
||||
*/
|
||||
private function register_main_integration()
|
||||
{
|
||||
$script_path = '/build/index.js';
|
||||
$style_path = '/build/style-index.css';
|
||||
|
||||
$script_url = PR_DHL_PLUGIN_DIR_URL.$script_path;
|
||||
$style_url = PR_DHL_PLUGIN_DIR_URL.$style_path;
|
||||
|
||||
$script_asset_path = PR_DHL_PLUGIN_DIR_PATH.'/build/index.asset.php';
|
||||
$script_asset = file_exists($script_asset_path)
|
||||
? require $script_asset_path
|
||||
: [
|
||||
'dependencies' => [],
|
||||
'version' => $this->get_file_version($script_path),
|
||||
];
|
||||
|
||||
wp_enqueue_style(
|
||||
'pr-dhl-preferred-services-integration',
|
||||
$style_url,
|
||||
[],
|
||||
$this->get_file_version($style_path)
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'pr-dhl-preferred-services-integration',
|
||||
$script_url,
|
||||
$script_asset['dependencies'],
|
||||
$script_asset['version'],
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations(
|
||||
'pr-dhl-preferred-services-integration',
|
||||
'dhl-for-woocommerce',
|
||||
PR_DHL_PLUGIN_DIR_PATH.'/lang'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers block styles for the block editor.
|
||||
*/
|
||||
private function register_styles()
|
||||
{
|
||||
$block_style_path = '/build/style-index.css';
|
||||
$block_style_url = PR_DHL_PLUGIN_DIR_URL.$block_style_path;
|
||||
|
||||
wp_enqueue_style(
|
||||
'pr-dhl-blocks-style',
|
||||
$block_style_url,
|
||||
[],
|
||||
$this->get_file_version($block_style_path)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to register block editor scripts.
|
||||
*
|
||||
* @param string $handle Script handle name.
|
||||
* @param string $script_path Path to the JS file.
|
||||
* @param string $asset_path Path to the asset file.
|
||||
*/
|
||||
private function register_block_script($handle, $script_path, $asset_path)
|
||||
{
|
||||
$script_url = PR_DHL_PLUGIN_DIR_URL.$script_path;
|
||||
$asset_file = PR_DHL_PLUGIN_DIR_PATH.$asset_path;
|
||||
$asset = file_exists($asset_file) ? require $asset_file : [
|
||||
'dependencies' => [],
|
||||
'version' => $this->get_file_version($script_path),
|
||||
];
|
||||
|
||||
wp_register_script(
|
||||
$handle,
|
||||
$script_url,
|
||||
$asset['dependencies'],
|
||||
$asset['version'],
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations(
|
||||
$handle,
|
||||
'dhl-for-woocommerce',
|
||||
PR_DHL_PLUGIN_DIR_PATH.'/lang'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to register frontend scripts.
|
||||
*
|
||||
* @param string $handle Script handle name.
|
||||
* @param string $script_path Path to the JS file.
|
||||
* @param string $asset_path Path to the asset file.
|
||||
*/
|
||||
private function register_frontend_script($handle, $script_path, $asset_path)
|
||||
{
|
||||
$script_url = PR_DHL_PLUGIN_DIR_URL.$script_path;
|
||||
$asset_file = PR_DHL_PLUGIN_DIR_PATH.$asset_path;
|
||||
$asset = file_exists($asset_file) ? require $asset_file : [
|
||||
'dependencies' => [],
|
||||
'version' => $this->get_file_version($script_path),
|
||||
];
|
||||
// Enqueue Google Maps
|
||||
$dhl_settings = PR_DHL()->get_shipping_dhl_settings();
|
||||
|
||||
wp_enqueue_script(
|
||||
$handle,
|
||||
$script_url,
|
||||
$asset['dependencies'],
|
||||
$asset['version'],
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations(
|
||||
$handle,
|
||||
'dhl-for-woocommerce',
|
||||
PR_DHL_PLUGIN_DIR_PATH.'/lang'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers AJAX actions.
|
||||
*/
|
||||
private function register_ajax_actions()
|
||||
{
|
||||
add_action('wp_ajax_pr_dhl_set_checkout_post_data', [$this, 'handle_set_checkout_post_data']);
|
||||
add_action('wp_ajax_nopriv_pr_dhl_set_checkout_post_data', [$this, 'handle_set_checkout_post_data']);
|
||||
add_action('wp_ajax_pr_dhl_get_preferred_days', [$this, 'handle_get_preferred_days']);
|
||||
add_action('wp_ajax_nopriv_pr_dhl_get_preferred_days', [$this, 'handle_get_preferred_days']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the frontend context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_script_handles()
|
||||
{
|
||||
return [
|
||||
'pr-dhl-preferred-services-integration',
|
||||
'pr-dhl-preferred-services-frontend',
|
||||
'pr-dhl-closest-drop-point-frontend',
|
||||
'pr-dhl-parcel-finder-frontend',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the editor context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_editor_script_handles()
|
||||
{
|
||||
return [
|
||||
'pr-dhl-preferred-services-integration',
|
||||
'pr-dhl-preferred-services-editor',
|
||||
'pr-dhl-closest-drop-point-editor',
|
||||
'pr-dhl-parcel-finder-editor',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to the block on the client side.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_script_data()
|
||||
{
|
||||
return [
|
||||
'pr-dhl-active' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes scripts with necessary data.
|
||||
*/
|
||||
private function localize_scripts()
|
||||
{
|
||||
// Fetch the shipping settings.
|
||||
$dhl_settings = $this->get_dhl_settings();
|
||||
|
||||
$localize_data = [
|
||||
'pluginUrl' => PR_DHL_PLUGIN_DIR_URL,
|
||||
'dhlSettings' => $dhl_settings,
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('pr_dhl_nonce'),
|
||||
'parcel_nonce' => wp_create_nonce('dhl_parcelfinder'),
|
||||
'DHL_ENGLISH_REGISTRATION_LINK' => DHL_ENGLISH_REGISTRATION_LINK,
|
||||
'DHL_GERMAN_REGISTRATION_LINK' => DHL_GERMAN_REGISTRATION_LINK,
|
||||
'locale' => get_locale(),
|
||||
'valid_countries' => ['SE', 'FI', 'BE', 'AT', 'FR'],
|
||||
'droppointAddressTypes' => PR_DHL()->get_droppoint_address_types(),
|
||||
];
|
||||
|
||||
// Localize the editor script.
|
||||
wp_localize_script(
|
||||
'pr-dhl-preferred-services-editor',
|
||||
'prDhlGlobals',
|
||||
$localize_data
|
||||
);
|
||||
|
||||
// Localize the frontend scripts.
|
||||
wp_localize_script(
|
||||
'pr-dhl-preferred-services-frontend',
|
||||
'prDhlGlobals',
|
||||
$localize_data
|
||||
);
|
||||
|
||||
// Localize the frontend scripts.
|
||||
wp_localize_script(
|
||||
'pr-dhl-closest-drop-point-frontend',
|
||||
'prDhlGlobals',
|
||||
$localize_data
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DHL settings that are relevant to the blocks.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function get_dhl_settings(): array
|
||||
{
|
||||
$dhl_settings = PR_DHL()->get_shipping_dhl_settings();
|
||||
|
||||
$settings_keys = [
|
||||
'dhl_display_packstation',
|
||||
'dhl_display_parcelshop',
|
||||
'dhl_display_post_office',
|
||||
'dhl_preferred_day',
|
||||
'dhl_preferred_location',
|
||||
'dhl_preferred_neighbour',
|
||||
'dhl_preferred_day_cost',
|
||||
'dhl_display_google_maps',
|
||||
'dhl_closest_drop_point',
|
||||
];
|
||||
|
||||
$filtered_settings = [];
|
||||
|
||||
foreach ($settings_keys as $key) {
|
||||
$value = $dhl_settings[$key] ?? false;
|
||||
|
||||
if ($value === 'yes') {
|
||||
$value = true;
|
||||
} elseif (! is_numeric($value)) {
|
||||
$value = false;
|
||||
}
|
||||
|
||||
// Remove 'dhl_' prefix from the key.
|
||||
$clean_key = str_replace('dhl_', '', $key);
|
||||
$filtered_settings[$clean_key] = $value;
|
||||
}
|
||||
|
||||
return $filtered_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file modified time as a cache buster if we're in dev mode.
|
||||
*
|
||||
* @param string $file Local path to the file.
|
||||
* @return string The cache buster value to use for the given file.
|
||||
*/
|
||||
protected function get_file_version($file)
|
||||
{
|
||||
$file_path = PR_DHL_PLUGIN_DIR_PATH.$file;
|
||||
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG && file_exists($file_path)) {
|
||||
return filemtime($file_path);
|
||||
}
|
||||
|
||||
return PR_DHL_VERSION;
|
||||
}
|
||||
|
||||
public function handle_set_checkout_post_data()
|
||||
{
|
||||
// Verify nonce
|
||||
if (! isset($_POST['nonce']) || ! wp_verify_nonce($_POST['nonce'], 'pr_dhl_nonce')) {
|
||||
wp_send_json_error(['message' => 'Invalid nonce'], 400);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
// Check if data is provided
|
||||
if (! isset($_POST['data']) || ! is_array($_POST['data'])) {
|
||||
wp_send_json_error(['message' => 'No data provided.'], 400);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
// Sanitize data
|
||||
$sanitized_data = array_map('sanitize_text_field', wp_unslash($_POST['data']));
|
||||
|
||||
// Store data in WooCommerce session
|
||||
WC()->session->set('pr_dhl_checkout_post_data', $sanitized_data);
|
||||
|
||||
wp_send_json_success(['message' => 'Data saved successfully.'], 200);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
public function handle_get_preferred_days()
|
||||
{
|
||||
// Verify nonce
|
||||
if (! isset($_POST['nonce']) || ! wp_verify_nonce($_POST['nonce'], 'pr_dhl_nonce')) {
|
||||
wp_send_json_error(['message' => 'Invalid nonce'], 400);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
// Retrieve post_data from WooCommerce session
|
||||
$order_data = WC()->session->get('pr_dhl_checkout_post_data');
|
||||
if (empty($order_data) || ! is_array($order_data)) {
|
||||
wp_send_json_error(['message' => 'No checkout data found.'], 400);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
// Use the order_data to get the preferred days
|
||||
$postcode = isset($order_data['shipping_postcode']) ? $order_data['shipping_postcode'] : '';
|
||||
$shipping_country = isset($order_data['shipping_country']) ? $order_data['shipping_country'] : '';
|
||||
|
||||
if (empty($postcode)) {
|
||||
wp_send_json_error(['message' => 'Postcode not found in checkout data.'], 400);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
try {
|
||||
$front_end_packet = new PR_DHL_Front_End_Paket;
|
||||
|
||||
// Call the validate_extra_services_available function with parameters
|
||||
$display_preferred = $front_end_packet->validate_extra_services_available(
|
||||
true,
|
||||
$shipping_country);
|
||||
|
||||
if (! $display_preferred) {
|
||||
wp_send_json_error(['message' => 'Preferred services not available.'], 400);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$preferred_day_time = PR_DHL()->get_dhl_preferred_day_time($postcode);
|
||||
wp_send_json_success(['preferredDays' => $preferred_day_time['preferred_day']], 200);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* DHL checkout Extend WC Core.
|
||||
*/
|
||||
if (! class_exists('PR_DHL_Extend_Block_core')) {
|
||||
|
||||
class PR_DHL_Extend_Block_core
|
||||
{
|
||||
/**
|
||||
* Plugin Identifier, unique to each plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name = 'pr-dhl';
|
||||
|
||||
/**
|
||||
* Bootstraps the class and hooks required data.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
add_action('woocommerce_store_api_checkout_update_order_from_request', [
|
||||
$this,
|
||||
'save_dhl_checkout_fields',
|
||||
], 10, 2);
|
||||
|
||||
// Register the update callback when WooCommerce Blocks is loaded
|
||||
add_action('init', [$this, 'register_store_api_callback']);
|
||||
|
||||
// Register fee calculation
|
||||
add_action('woocommerce_cart_calculate_fees', [$this, 'add_preferred_day_fee']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the dhl fields to the order's metadata.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_dhl_checkout_fields(WC_Order $order, WP_REST_Request $request)
|
||||
{
|
||||
$pr_dhl_request_data = isset($request['extensions'][$this->name]) ? $request['extensions'][$this->name] : [];
|
||||
$dhl_label_options = [];
|
||||
if (! empty($pr_dhl_request_data['preferredDay'])) {
|
||||
$dhl_label_options['pr_dhl_preferred_day'] = wc_clean($pr_dhl_request_data['preferredDay']);
|
||||
}
|
||||
|
||||
$settings = PR_DHL()->get_shipping_dhl_settings();
|
||||
$has_location = ! empty($settings['dhl_preferred_location']) && $settings['dhl_preferred_location'] === 'yes';
|
||||
$has_neighbor = ! empty($settings['dhl_preferred_neighbour']) && $settings['dhl_preferred_neighbour'] === 'yes';
|
||||
$both_enabled = $has_location && $has_neighbor;
|
||||
|
||||
$dropp_off_choice = isset($pr_dhl_request_data['preferredLocationNeighbor'])
|
||||
? wc_clean($pr_dhl_request_data['preferredLocationNeighbor'])
|
||||
: '';
|
||||
$dropp_off_choice = in_array($dropp_off_choice, [
|
||||
'none',
|
||||
'location',
|
||||
'neighbor',
|
||||
], true) ? $dropp_off_choice : '';
|
||||
|
||||
if ($both_enabled) {
|
||||
|
||||
if ($dropp_off_choice === 'location') {
|
||||
$loc = isset($pr_dhl_request_data['preferredLocation']) ? trim($pr_dhl_request_data['preferredLocation']) : '';
|
||||
if ($loc === '') {
|
||||
throw new Exception(__('Please enter the preferred location.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
$dhl_label_options['pr_dhl_preferred_location_neighbor'] = 'location';
|
||||
$dhl_label_options['pr_dhl_preferred_location'] = wc_clean($loc);
|
||||
|
||||
} elseif ($dropp_off_choice === 'neighbor') {
|
||||
$nm = isset($pr_dhl_request_data['preferredNeighborName']) ? trim($pr_dhl_request_data['preferredNeighborName']) : '';
|
||||
$adr = isset($pr_dhl_request_data['preferredNeighborAddress']) ? trim($pr_dhl_request_data['preferredNeighborAddress']) : '';
|
||||
if ($nm === '' || $adr === '') {
|
||||
throw new Exception(__('Please enter the preferred neighbor name and address.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
$dhl_label_options['pr_dhl_preferred_location_neighbor'] = 'neighbor';
|
||||
$dhl_label_options['pr_dhl_preferred_neighbour_name'] = wc_clean($nm);
|
||||
$dhl_label_options['pr_dhl_preferred_neighbour_address'] = wc_clean($adr);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if ($has_location) {
|
||||
$loc = isset($pr_dhl_request_data['preferredLocation']) ? trim($pr_dhl_request_data['preferredLocation']) : '';
|
||||
if ($loc !== '') {
|
||||
$dhl_label_options['pr_dhl_preferred_location_neighbor'] = 'location';
|
||||
$dhl_label_options['pr_dhl_preferred_location'] = wc_clean($loc);
|
||||
}
|
||||
} elseif ($has_neighbor) {
|
||||
$nm = isset($pr_dhl_request_data['preferredNeighborName']) ? trim($pr_dhl_request_data['preferredNeighborName']) : '';
|
||||
$adr = isset($pr_dhl_request_data['preferredNeighborAddress']) ? trim($pr_dhl_request_data['preferredNeighborAddress']) : '';
|
||||
if ($nm !== '' || $adr !== '') {
|
||||
if ($nm === '' || $adr === '') {
|
||||
throw new Exception(__('Please enter the preferred neighbor name and address.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
$dhl_label_options['pr_dhl_preferred_location_neighbor'] = 'neighbor';
|
||||
$dhl_label_options['pr_dhl_preferred_neighbour_name'] = wc_clean($nm);
|
||||
$dhl_label_options['pr_dhl_preferred_neighbour_address'] = wc_clean($adr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($pr_dhl_request_data['closest_drop_point'])) {
|
||||
$dhl_label_options['pr_dhl_cdp_delivery'] = wc_clean($pr_dhl_request_data['closest_drop_point']);
|
||||
}
|
||||
|
||||
if (! empty($dhl_label_options)) {
|
||||
PR_DHL()->get_pr_dhl_wc_order()->save_dhl_label_items($order->get_id(), $dhl_label_options);
|
||||
}
|
||||
|
||||
// The Post Number only applies to Packstation / Postfiliale deliveries. Persist it only
|
||||
// when the address type or the shipping address itself is a droppoint; otherwise drop any
|
||||
// stale value so a Regular Address never carries a Post Number to the DHL API. Mirrors the
|
||||
// classic checkout's clear_post_number_for_regular_address().
|
||||
$address_type = isset($pr_dhl_request_data['addressType']) ? wc_clean($pr_dhl_request_data['addressType']) : '';
|
||||
$shipping_postnum = isset($pr_dhl_request_data['postNumber']) ? sanitize_text_field($pr_dhl_request_data['postNumber']) : '';
|
||||
$shipping_address_1 = $order->get_shipping_address_1();
|
||||
|
||||
if (PR_DHL()->is_droppoint($address_type, $shipping_address_1)) {
|
||||
$order->update_meta_data('_shipping_dhl_postnum', $shipping_postnum);
|
||||
} else {
|
||||
$order->delete_meta_data('_shipping_dhl_postnum');
|
||||
}
|
||||
|
||||
// Save the order to persist changes.
|
||||
$order->save();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the update callback with WooCommerce Store API
|
||||
*/
|
||||
public function register_store_api_callback()
|
||||
{
|
||||
if (function_exists('woocommerce_store_api_register_update_callback')) {
|
||||
woocommerce_store_api_register_update_callback(
|
||||
[
|
||||
'namespace' => $this->name,
|
||||
'callback' => [$this, 'store_api_callback'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for the Store API update
|
||||
*
|
||||
* @param array $data Data sent from the client.
|
||||
*/
|
||||
public function store_api_callback($data)
|
||||
{
|
||||
if (isset($data['action']) && $data['action'] === 'update_preferred_day_fee') {
|
||||
$price = isset($data['price']) ? floatval($data['price']) : 0;
|
||||
$label = isset($data['label']) ? sanitize_text_field($data['label']) : '';
|
||||
|
||||
if ($price > 0 && $label !== '') {
|
||||
// Store the fee amount and label in session
|
||||
WC()->session->set('pr_dhl_preferred_day_fee', $price);
|
||||
WC()->session->set('pr_dhl_preferred_day_label', $label);
|
||||
} else {
|
||||
WC()->session->__unset('pr_dhl_preferred_day_fee');
|
||||
WC()->session->__unset('pr_dhl_preferred_day_label');
|
||||
}
|
||||
} else {
|
||||
|
||||
WC()->session->__unset('pr_dhl_preferred_day_fee');
|
||||
WC()->session->__unset('pr_dhl_preferred_day_label');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the preferred day fee to the WooCommerce cart.
|
||||
*
|
||||
* @param WC_Cart $cart The WooCommerce cart object.
|
||||
*/
|
||||
public function add_preferred_day_fee($cart)
|
||||
{
|
||||
if (is_admin() && ! defined('DOING_AJAX')) {
|
||||
return;
|
||||
}
|
||||
if (! $cart->needs_shipping()) {
|
||||
WC()->session->__unset('pr_dhl_preferred_day_fee');
|
||||
WC()->session->__unset('pr_dhl_preferred_day_label');
|
||||
|
||||
return;
|
||||
}
|
||||
// Get the fee amount and label from session
|
||||
$fee_amount = WC()->session->get('pr_dhl_preferred_day_fee', 0);
|
||||
$fee_label = WC()->session->get('pr_dhl_preferred_day_label', '');
|
||||
|
||||
// Remove existing DHL fees if they exist
|
||||
$new_fees = [];
|
||||
foreach ($cart->get_fees() as $fee) {
|
||||
if (strpos($fee->name, 'DHL') === false) {
|
||||
$new_fees[] = $fee;
|
||||
}
|
||||
}
|
||||
$cart->fees_api()->set_fees($new_fees);
|
||||
|
||||
if ($fee_amount > 0 && ! empty($fee_label)) {
|
||||
// Add the fee to the cart
|
||||
$cart->add_fee($fee_label, $fee_amount, true, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema;
|
||||
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
|
||||
use Automattic\WooCommerce\StoreApi\StoreApi;
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Extend store endpont.
|
||||
*
|
||||
* @category Shipping
|
||||
*/
|
||||
if (! class_exists('PR_DHL_Extend_Store_Endpoint')) {
|
||||
|
||||
/**
|
||||
* DHL checkout Extend Store API.
|
||||
*/
|
||||
class PR_DHL_Extend_Store_Endpoint
|
||||
{
|
||||
/**
|
||||
* Stores Rest Extending instance.
|
||||
*
|
||||
* @var ExtendRestApi
|
||||
*/
|
||||
private static $extend;
|
||||
|
||||
/**
|
||||
* Plugin Identifier, unique to each plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IDENTIFIER = 'pr-dhl';
|
||||
|
||||
/**
|
||||
* Bootstraps the class and hooks required data.
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
self::$extend = StoreApi::container()->get(ExtendSchema::class);
|
||||
self::extend_store();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the actual data into each endpoint.
|
||||
*/
|
||||
public static function extend_store()
|
||||
{
|
||||
|
||||
if (is_callable([self::$extend, 'register_endpoint_data'])) {
|
||||
self::$extend->register_endpoint_data(
|
||||
[
|
||||
'endpoint' => CheckoutSchema::IDENTIFIER,
|
||||
'namespace' => self::IDENTIFIER,
|
||||
'schema_callback' => ['PR_DHL_Extend_Store_Endpoint', 'extend_checkout_schema'],
|
||||
'schema_type' => ARRAY_A,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register DHL checkout schema into the Checkout endpoint.
|
||||
*
|
||||
* @return array Registered schema.
|
||||
*/
|
||||
public static function extend_checkout_schema()
|
||||
{
|
||||
return [
|
||||
'preferredDay' => [
|
||||
'description' => 'Preferred delivery day for the shipment',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
'preferredLocationNeighbor' => [
|
||||
'description' => 'Preferred location or neighbor for delivery',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
'preferredLocation' => [
|
||||
'description' => 'Preferred drop-off location for the shipment',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
'preferredNeighborName' => [
|
||||
'description' => 'Name of the preferred neighbor for delivery',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
'preferredNeighborAddress' => [
|
||||
'description' => 'Address of the preferred neighbor for delivery',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
'addressType' => [
|
||||
'description' => 'Address Type',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
'postNumber' => [
|
||||
'description' => 'Post Number',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
'closest_drop_point' => [
|
||||
'description' => 'Closest Drop Point for delivery',
|
||||
'type' => 'string',
|
||||
'context' => ['view', 'edit'],
|
||||
'readonly' => true,
|
||||
'arg_options' => [
|
||||
'validate_callback' => function ($value) {
|
||||
return is_string($value);
|
||||
},
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
use PR_DHL\lib\PDFMerger\PDFMerger;
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (! class_exists('PR_DHL_Libraryloader')) {
|
||||
|
||||
class PR_DHL_Libraryloader
|
||||
{
|
||||
const CLASS_PDF_MERGER = 'pdf_merger';
|
||||
|
||||
private static $instances = [];
|
||||
|
||||
protected $include_path = null;
|
||||
|
||||
protected $file_path = null;
|
||||
|
||||
protected $loaded = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Set paths
|
||||
$this->include_path = PR_DHL_PLUGIN_DIR_PATH.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR;
|
||||
$this->file_path = $this->include_path.'PDFMerger'.DIRECTORY_SEPARATOR.'PDFMerger.php';
|
||||
$this->file_path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $this->file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a singleton instance of the called class
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
|
||||
$class = get_called_class();
|
||||
if (! isset(self::$instances[$class])) {
|
||||
self::$instances[$class] = new static;
|
||||
}
|
||||
|
||||
return self::$instances[$class];
|
||||
|
||||
}
|
||||
|
||||
public function get_pdf_merger()
|
||||
{
|
||||
if (! class_exists('PR_DHL\lib\PDFMerger\PDFMerger')) {
|
||||
$loaded = $this->include_file($this->file_path);
|
||||
|
||||
if (! $loaded) {
|
||||
return null;
|
||||
}
|
||||
$this->loaded[] = self::CLASS_PDF_MERGER;
|
||||
}
|
||||
|
||||
// Something very unexpected happened, return
|
||||
if (! class_exists('PR_DHL\lib\PDFMerger\PDFMerger')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PDFMerger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include a file if it exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function include_file($path)
|
||||
{
|
||||
|
||||
if (file_exists($path)) {
|
||||
// Supress errors of third party libraries
|
||||
$status = @include_once $path;
|
||||
if ($status !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
dev/dhl-for-woocommerce/includes/class-pr-dhl-logger.php
Normal file
70
dev/dhl-for-woocommerce/includes/class-pr-dhl-logger.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
class PR_DHL_Logger
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $debug;
|
||||
|
||||
/**
|
||||
* WC_DHL_Logger constructor.
|
||||
*
|
||||
* @param WC_XR_debug $debug
|
||||
*/
|
||||
public function __construct($debug)
|
||||
{
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if logging is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled()
|
||||
{
|
||||
|
||||
// Check if debug is on
|
||||
if ($this->debug === 'yes') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the message to log
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
public function write($message)
|
||||
{
|
||||
|
||||
// Check if enabled
|
||||
if ($this->is_enabled()) {
|
||||
|
||||
// Logger object
|
||||
$wc_logger = new WC_Logger;
|
||||
|
||||
// Add to logger
|
||||
$wc_logger->add('DHL', $message);
|
||||
}
|
||||
}
|
||||
|
||||
public function get_log_url()
|
||||
{
|
||||
return admin_url('admin.php?page=wc-status&tab=logs');
|
||||
/*
|
||||
$log_path = wc_get_log_file_path( 'DHL' );
|
||||
$upload_path = wp_upload_dir();
|
||||
|
||||
$log_url = str_replace( $upload_path['basedir'], $upload_path['baseurl'], $log_path );
|
||||
|
||||
return $log_url;*/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,400 @@
|
|||
<?php
|
||||
|
||||
// Exit if accessed directly or class already exists
|
||||
if (! defined('ABSPATH') || class_exists('PR_DHL_WC_Method_Deutsche_Post', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class PR_DHL_WC_Method_Deutsche_Post extends WC_Shipping_Method
|
||||
{
|
||||
/**
|
||||
* Init and hook in the integration.
|
||||
*/
|
||||
public function __construct($instance_id = 0)
|
||||
{
|
||||
$this->id = 'pr_dhl_dp';
|
||||
$this->instance_id = absint($instance_id);
|
||||
$this->method_title = esc_html__('Deutsche Post International', 'dhl-for-woocommerce');
|
||||
|
||||
/* translators: %1$s is the link to the Deutsche Post contact page, %2$s is the closing HTML tag for the link */
|
||||
$this->method_description = sprintf(
|
||||
esc_html__('To start creating Deutsche Post shipping labels and return back a tracking number to your customers, please fill in your user credentials as provided by Deutsche Post. Not yet a customer? Please get a quote %1$shere%2$s.', 'dhl-for-woocommerce'),
|
||||
'<a href="https://www.deutschepost.com/en/business-customers/contact.html" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
// Load the settings.
|
||||
$this->init_form_fields();
|
||||
$this->init_settings();
|
||||
|
||||
add_action('woocommerce_update_options_shipping_'.$this->id, [$this, 'process_admin_options']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message
|
||||
*
|
||||
* @return string Error
|
||||
*/
|
||||
private function get_message($message, $type = 'notice notice-error is-dismissible')
|
||||
{
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="<?php echo esc_html($type); ?>">
|
||||
<p><?php echo esc_html($message); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize integration settings form fields.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function init_form_fields()
|
||||
{
|
||||
|
||||
$log_path = PR_DHL()->get_log_url();
|
||||
|
||||
try {
|
||||
|
||||
$dhl_obj = PR_DHL()->get_dhl_factory();
|
||||
$select_dhl_product_int = $dhl_obj->get_dhl_products_international();
|
||||
} catch (Exception $e) {
|
||||
PR_DHL()->log_msg(esc_html__('Deutsche Post Products not displaying - ', 'dhl-for-woocommerce').$e->getMessage());
|
||||
}
|
||||
|
||||
$weight_units = get_option('woocommerce_weight_unit');
|
||||
|
||||
$this->form_fields = [
|
||||
'dhl_api' => [
|
||||
'title' => esc_html__('Account and API Settings', 'dhl-for-woocommerce'),
|
||||
'type' => 'title',
|
||||
'description' => esc_html__(
|
||||
'Please configure your account and API settings with Deutschepost International.',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'class' => '',
|
||||
],
|
||||
'dhl_account_num' => [
|
||||
'title' => esc_html__('Account Number (EKP)', 'dhl-for-woocommerce'),
|
||||
'type' => 'text',
|
||||
'description' => esc_html__('Your account number (9; 10 or 15 digits, numerical), also called "EKP“. This will be provided by your local Deutsche Post sales organization.', 'dhl-for-woocommerce'),
|
||||
'desc_tip' => true,
|
||||
'default' => '',
|
||||
'placeholder' => '1234567890',
|
||||
'custom_attributes' => ['maxlength' => '10'],
|
||||
],
|
||||
'dhl_contact_name' => [
|
||||
'title' => esc_html__('Contact Name', 'dhl-for-woocommerce'),
|
||||
'type' => 'text',
|
||||
'description' => esc_html__('Required for all customers. The name of the merchant, used as contact information on the Waybill.', 'dhl-for-woocommerce'),
|
||||
'desc_tip' => true,
|
||||
'default' => '',
|
||||
'placeholder' => 'Contact Name',
|
||||
],
|
||||
'dhl_contact_phone_number' => [
|
||||
'title' => esc_html__('Contact Phone Number', 'dhl-for-woocommerce'),
|
||||
'type' => 'text',
|
||||
'description' => esc_html__('Required for DHL Express customers. The phone number of the merchant, used as contact information on the Waybill.', 'dhl-for-woocommerce'),
|
||||
'desc_tip' => true,
|
||||
'default' => '',
|
||||
'placeholder' => '+4935120681234',
|
||||
],
|
||||
'dhl_api_key' => [
|
||||
'title' => esc_html__('Client Id', 'dhl-for-woocommerce'),
|
||||
'type' => 'text',
|
||||
'description' => esc_html__(
|
||||
'The client ID (a 36 digits alphanumerical string made from 5 blocks) is required for authentication and is provided to you within your contract.',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'default' => '',
|
||||
],
|
||||
'dhl_api_secret' => [
|
||||
'title' => esc_html__('Client Secret', 'dhl-for-woocommerce'),
|
||||
'type' => 'text',
|
||||
'description' => esc_html__(
|
||||
'The client secret (also a 36 digits alphanumerical string made from 5 blocks) is required for authentication (together with the client ID) and creates the tokens needed to ensure secure access. It is part of your contract provided by your Deutsche Post sales partner.',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'default' => '',
|
||||
],
|
||||
'dhl_sandbox' => [
|
||||
'title' => esc_html__('Sandbox Mode', 'dhl-for-woocommerce'),
|
||||
'type' => 'checkbox',
|
||||
'label' => esc_html__('Enable Sandbox Mode', 'dhl-for-woocommerce'),
|
||||
'default' => 'no',
|
||||
'description' => esc_html__(
|
||||
'Please, tick here if you want to test the plug-in installation against the Deutsche Post Sandbox Environment. Labels generated via Sandbox cannot be used for shipping and you need to enter your client ID and client secret for the Sandbox environment instead of the ones for production!',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
],
|
||||
'dhl_test_connection_button' => [
|
||||
'title' => PR_DHL_BUTTON_TEST_CONNECTION,
|
||||
'type' => 'button',
|
||||
'custom_attributes' => [
|
||||
'onclick' => "dhlTestConnection('#woocommerce_pr_dhl_dp_dhl_test_connection_button');",
|
||||
],
|
||||
'description' => esc_html__(
|
||||
'Press the button for testing the connection against our Deutsche Post (depending on the selected environment this test is being done against the Sandbox or the Production Environment).',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
],
|
||||
'dhl_debug' => [
|
||||
'title' => esc_html__('Debug Log', 'dhl-for-woocommerce'),
|
||||
'type' => 'checkbox',
|
||||
'label' => esc_html__('Enable logging', 'dhl-for-woocommerce'),
|
||||
'default' => 'yes',
|
||||
/* translators: %1$s is the link to the log file, %2$s is the closing HTML tag for the link */
|
||||
'description' => sprintf(
|
||||
esc_html__('A log file containing the communication to the Deutsche Post server will be maintained if this option is checked. This can be used in case of technical issues and can be found %1$shere%2$s.', 'dhl-for-woocommerce'),
|
||||
'<a href="'.esc_url($log_path).'" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
$this->form_fields += [
|
||||
'dhl_pickup_dist' => [
|
||||
'title' => esc_html__('Shipping', 'dhl-for-woocommerce'),
|
||||
'type' => 'title',
|
||||
'description' => esc_html__('Please configure your shipping parameters underneath.', 'dhl-for-woocommerce'),
|
||||
'class' => '',
|
||||
],
|
||||
'dhl_default_product_int' => [
|
||||
'title' => esc_html__('International Default Service', 'dhl-for-woocommerce'),
|
||||
'type' => 'select',
|
||||
'description' => esc_html__(
|
||||
'Please select your default Deutsche Post shipping service for cross-border shippments that you want to offer to your customers (you can always change this within each individual order afterwards).',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'options' => $select_dhl_product_int,
|
||||
'class' => 'wc-enhanced-select',
|
||||
],
|
||||
'dhl_add_weight_type' => [
|
||||
'title' => esc_html__('Additional Weight Type', 'dhl-for-woocommerce'),
|
||||
'type' => 'select',
|
||||
'description' => esc_html__(
|
||||
'Select whether to add an absolute weight amount or percentage amount to the total product weight.',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'options' => [
|
||||
'absolute' => 'Absolute',
|
||||
'percentage' => 'Percentage',
|
||||
],
|
||||
'class' => 'wc-enhanced-select',
|
||||
],
|
||||
'dhl_add_weight' => [
|
||||
/* translators: %s is the unit of weight (e.g., kg, lbs) */
|
||||
'title' => sprintf(
|
||||
esc_html__('Additional Weight (%s or %%)', 'dhl-for-woocommerce'),
|
||||
$weight_units
|
||||
),
|
||||
'type' => 'text',
|
||||
'description' => esc_html__(
|
||||
'Add extra weight in addition to the products. Either an absolute amount or percentage (e.g. 10 for 10%).',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'default' => '',
|
||||
'placeholder' => '',
|
||||
'class' => 'wc_input_decimal',
|
||||
],
|
||||
|
||||
/*
|
||||
'dhl_packet_return' => array(
|
||||
'title' => esc_html__( 'Packet Return', 'dhl-for-woocommerce' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => esc_html__( 'Enable', 'dhl-for-woocommerce' ),
|
||||
'default' => 'no',
|
||||
'description' => esc_html__(
|
||||
'Please note that Packet Return needs to be activated by Deutsche Post. Please get in touch with your local DP Customer Service for more details.',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
),
|
||||
*/
|
||||
'dhl_tracking_note' => [
|
||||
'title' => esc_html__('Tracking Note', 'dhl-for-woocommerce'),
|
||||
'type' => 'checkbox',
|
||||
'label' => esc_html__('Make Private', 'dhl-for-woocommerce'),
|
||||
'default' => 'no',
|
||||
'description' => esc_html__(
|
||||
'Please, tick here to not send an email to the customer when the tracking number is added to the order.',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
],
|
||||
'dhl_tracking_note_txt' => [
|
||||
'title' => esc_html__('Tracking Note', 'dhl-for-woocommerce'),
|
||||
'type' => 'textarea',
|
||||
'description' => esc_html__(
|
||||
'Set the custom text when adding the tracking number to the order notes. {tracking-link} is where the tracking number will be set.',
|
||||
'dhl-for-woocommerce'
|
||||
),
|
||||
'desc_tip' => false,
|
||||
'default' => esc_html__('Deutsche Post Tracking Number: {tracking-link}', 'dhl-for-woocommerce'),
|
||||
],
|
||||
];
|
||||
|
||||
$this->form_fields += [
|
||||
'dhl_label_section' => [
|
||||
'title' => esc_html__('Label options', 'dhl-for-woocommerce'),
|
||||
'type' => 'title',
|
||||
'description' => esc_html__('Options for configuring your label preferences', 'dhl-for-woocommerce'),
|
||||
],
|
||||
'dhl_label_ref' => [
|
||||
'title' => esc_html__('Label Reference', 'dhl-for-woocommerce'),
|
||||
'type' => 'text',
|
||||
'custom_attributes' => ['maxlength' => '35'],
|
||||
/* translators: %s is the placeholder for the order ID and customer email */
|
||||
'description' => sprintf(
|
||||
esc_html__('Use "%1$s" to send the order id as a reference and "%2$s" to send the customer email. This text is limited to 35 characters.', 'dhl-for-woocommerce'),
|
||||
'{order_id}',
|
||||
'{email}'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'default' => '{order_id}',
|
||||
],
|
||||
'dhl_label_ref_2' => [
|
||||
'title' => esc_html__('Label Reference 2', 'dhl-for-woocommerce'),
|
||||
'type' => 'text',
|
||||
'custom_attributes' => ['maxlength' => '35'],
|
||||
/* translators: %s is the placeholder for the order ID and customer email */
|
||||
'description' => sprintf(
|
||||
esc_html__('Use "%1$s" to send the order id as a reference and "%2$s" to send the customer email. This text is limited to 35 characters.', 'dhl-for-woocommerce'),
|
||||
'{order_id}',
|
||||
'{email}'
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'default' => '{email}',
|
||||
],
|
||||
];
|
||||
|
||||
$this->form_fields += [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Button HTML.
|
||||
*
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $data
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_button_html($key, $data)
|
||||
{
|
||||
$field = $this->plugin_id.$this->id.'_'.$key;
|
||||
$defaults = [
|
||||
'class' => 'button-secondary',
|
||||
'css' => '',
|
||||
'custom_attributes' => [],
|
||||
'desc_tip' => false,
|
||||
'description' => '',
|
||||
'title' => '',
|
||||
];
|
||||
|
||||
$data = wp_parse_args($data, $defaults);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<tr valign="top">
|
||||
<th scope="row" class="titledesc">
|
||||
<label for="<?php echo esc_attr($field); ?>"><?php echo esc_html($data['title']); ?></label>
|
||||
<?php echo $this->get_tooltip_html($data); ?>
|
||||
</th>
|
||||
<td class="forminp">
|
||||
<fieldset>
|
||||
<legend class="screen-reader-text"><span><?php echo esc_html($data['title']); ?></span>
|
||||
</legend>
|
||||
<button class="<?php echo esc_attr($data['class']); ?>" type="button"
|
||||
name="<?php echo esc_attr($field); ?>" id="<?php echo esc_attr($field); ?>"
|
||||
style="
|
||||
<?php
|
||||
echo esc_attr(
|
||||
$data['css']
|
||||
);
|
||||
?>
|
||||
" <?php echo $this->get_custom_attribute_html($data); ?>>
|
||||
<?php
|
||||
echo esc_html($data['title']);
|
||||
?>
|
||||
</button>
|
||||
<?php echo $this->get_description_html($data); ?>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the contact phone number field
|
||||
*
|
||||
* @see validate_settings_fields()
|
||||
*/
|
||||
public function validate_dhl_contact_phone_number_field($key, $value)
|
||||
{
|
||||
|
||||
$post_data = $this->get_post_data();
|
||||
$account_num = $post_data[$this->plugin_id.$this->id.'_'.'dhl_account_num'];
|
||||
|
||||
$first_nums = ['1', '3', '4'];
|
||||
|
||||
foreach ($first_nums as $num) {
|
||||
if (substr($account_num, 0, 1) == $num) {
|
||||
|
||||
if (empty($value)) {
|
||||
|
||||
$msg = esc_html__('Contact Phone Number required, please add in settings.', 'dhl-for-woocommerce');
|
||||
echo $this->get_message($msg);
|
||||
throw new Exception(esc_html($msg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes and saves options.
|
||||
* If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out.
|
||||
*/
|
||||
public function process_admin_options()
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
$dhl_obj = PR_DHL()->get_dhl_factory();
|
||||
$dhl_obj->dhl_reset_connection();
|
||||
} catch (Exception $e) {
|
||||
|
||||
echo esc_html($this->get_message(esc_html__('Could not reset connection: ', 'dhl-for-woocommerce').esc_html($e->getMessage())));
|
||||
// throw $e;
|
||||
}
|
||||
|
||||
return parent::process_admin_options();
|
||||
}
|
||||
}
|
||||
1311
dev/dhl-for-woocommerce/includes/class-pr-dhl-wc-method-paket.php
Normal file
1311
dev/dhl-for-woocommerce/includes/class-pr-dhl-wc-method-paket.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce DHL Add DHL Parcel (Legacy) Notice
|
||||
*
|
||||
* @category Admin Notice
|
||||
*
|
||||
* @author Shin Ho
|
||||
*/
|
||||
if (! class_exists('PR_DHL_WC_Notice_Legacy_Parcel')) {
|
||||
|
||||
class PR_DHL_WC_Notice_Legacy_Parcel
|
||||
{
|
||||
const NOTICE_TAG_MIGRATE = 'dhlpwc_migrate_notice';
|
||||
|
||||
const NOTICE_TAG_MIGRATE_FOREVER = 'dhlpwc_migrate_notice_forever';
|
||||
|
||||
/**
|
||||
* Init and hook in the integration.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (! is_admin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_option(self::NOTICE_TAG_MIGRATE_FOREVER, null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->check_eligible_country()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->check_was_enabled_config()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action('admin_enqueue_scripts', [$this, 'load_scripts']);
|
||||
|
||||
add_action('admin_notices', [$this, 'show_migrate_notice']);
|
||||
add_action('wp_ajax_dhl_legacy_parcel_dismiss_migrate_notice', [$this, 'dismiss_notice']);
|
||||
add_action('wp_ajax_dhl_legacy_parcel_dismiss_migrate_notice_forever', [$this, 'dismiss_notice_forever']);
|
||||
}
|
||||
|
||||
public function show_migrate_notice()
|
||||
{
|
||||
if (get_site_transient(self::NOTICE_TAG_MIGRATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$this->is_home_screen() ||
|
||||
$this->is_ordergrid_screen() ||
|
||||
$this->is_order_screen() ||
|
||||
$this->is_wc_settings_screen()
|
||||
) {
|
||||
?>
|
||||
|
||||
<div class="notice notice-warning is-dismissible dhl-legacy-parcel-dismiss-migrate-notice">
|
||||
<div style="position: absolute;">
|
||||
<img src="<?php echo esc_url(PR_DHL_PLUGIN_DIR_URL).'/assets/img/icon.svg'; ?>" class="plugin-icon" style="width: 85px; height: 85px; padding-right: 20px;" alt="">
|
||||
</div>
|
||||
<div style="position:relative; left: 105px; margin: 0 105px 0 0; padding: 0 0 20px 20px;">
|
||||
<span>
|
||||
<h2><?php esc_html_e('DHL Parcel for WooCommerce notice', 'dhl-for-woocommerce'); ?></h2>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %1$s is the link to install the new plugin, %2$s is the closing HTML tag for the link */
|
||||
esc_html__('DHL Parcel services are no longer available in this plugin. To continue using Parcel services, please install the new plugin %1$shere%2$s.', 'dhl-for-woocommerce'),
|
||||
'<a href="'.esc_url(admin_url('plugin-install.php?s=DHL Parcel for WooCommerce dhlpwc&tab=search&type=term')).'">',
|
||||
'</a>'
|
||||
)
|
||||
?>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<a href="#" id="dhl-legacy-parcel-dismiss-migrate-notice-forever">
|
||||
<b><?php esc_html_e('Click here to never show this again', 'dhl-for-woocommerce'); ?></b>
|
||||
</a>
|
||||
<div class="clear"></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
public function dismiss_notice()
|
||||
{
|
||||
check_ajax_referer('pr-dhl-legacy-parcel-dismiss-notice', 'security');
|
||||
|
||||
// Low level priority
|
||||
$time = 14 * DAY_IN_SECONDS;
|
||||
set_site_transient(self::NOTICE_TAG_MIGRATE, true, $time);
|
||||
|
||||
// Send JSON response
|
||||
wp_send_json(
|
||||
[
|
||||
'status' => 'success',
|
||||
'data' => [],
|
||||
'message' => null,
|
||||
],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
public function dismiss_notice_forever()
|
||||
{
|
||||
check_ajax_referer('pr-dhl-legacy-parcel-dismiss-notice', 'security');
|
||||
|
||||
update_option(self::NOTICE_TAG_MIGRATE_FOREVER, true);
|
||||
wp_send_json(
|
||||
[
|
||||
'status' => 'success',
|
||||
'data' => [],
|
||||
'message' => null,
|
||||
],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
protected function check_was_enabled_config()
|
||||
{
|
||||
$shipping_method = get_option('woocommerce_dhlpwc_settings');
|
||||
|
||||
if (empty($shipping_method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($shipping_method['enable_all'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($shipping_method['enable_all'] != 'yes') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($shipping_method['user_id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($shipping_method['key'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($shipping_method['account_id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function check_eligible_country()
|
||||
{
|
||||
$country_code = wc_get_base_location();
|
||||
|
||||
if (! in_array(
|
||||
$country_code['country'],
|
||||
[
|
||||
'NL',
|
||||
'BE',
|
||||
'LU',
|
||||
]
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function is_home_screen()
|
||||
{
|
||||
if (! function_exists('get_current_screen')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if (! isset($screen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($screen->base !== 'dashboard') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function is_ordergrid_screen()
|
||||
{
|
||||
if (! function_exists('get_current_screen')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if (! isset($screen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($screen->base !== 'edit' || $screen->post_type !== 'shop_order') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function is_order_screen()
|
||||
{
|
||||
if (! function_exists('get_current_screen')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if (! isset($screen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($screen->base !== 'post' || $screen->post_type !== 'shop_order') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function is_wc_settings_screen()
|
||||
{
|
||||
if (! function_exists('get_current_screen')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if (! isset($screen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($screen->base !== 'woocommerce_page_wc-settings') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function load_scripts()
|
||||
{
|
||||
if (
|
||||
$this->is_home_screen() ||
|
||||
$this->is_ordergrid_screen() ||
|
||||
$this->is_order_screen() ||
|
||||
$this->is_wc_settings_screen()
|
||||
) {
|
||||
$dismiss_data = [
|
||||
'security' => wp_create_nonce('pr-dhl-legacy-parcel-dismiss-notice'),
|
||||
];
|
||||
|
||||
wp_enqueue_script('wc-shipment-dhl-legacy-parcel-dismiss-notice-js', PR_DHL_PLUGIN_DIR_URL.'/assets/js/pr-dhl-notice-legacy-parcel.js', ['jquery'], PR_DHL_VERSION);
|
||||
wp_localize_script('wc-shipment-dhl-legacy-parcel-dismiss-notice-js', 'dhl_legacy_parcel_dismiss_notice', $dismiss_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
/**
|
||||
* SOAP API Removed - post-upgrade migration notice.
|
||||
*
|
||||
* DHL retired its Business Customer Shipping (Geschaeftskundenversand) SOAP API, so this
|
||||
* plugin now talks to the DHL Parcel DE Shipping REST API only. Stores that were still on
|
||||
* SOAP are switched to REST automatically, so this one-time, dismissible notice asks the
|
||||
* admin to re-check their REST API credentials so label creation keeps working.
|
||||
*
|
||||
* Shown only to DE stores that had the plugin configured and were NOT already on REST.
|
||||
*/
|
||||
|
||||
use PR\DHL\Utils\API_Utils;
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (! class_exists('PR_DHL_WC_Notice_SOAP_Removed')) {
|
||||
|
||||
class PR_DHL_WC_Notice_SOAP_Removed
|
||||
{
|
||||
/**
|
||||
* Option flag set once the admin dismisses the notice.
|
||||
*/
|
||||
const DISMISSED_OPTION = 'pr_dhl_soap_removed_notice_dismissed';
|
||||
|
||||
/**
|
||||
* Nonce action for the dismiss AJAX request.
|
||||
*/
|
||||
const NONCE_ACTION = 'pr-dhl-soap-removed-dismiss';
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (! is_admin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action('admin_notices', [__CLASS__, 'render']);
|
||||
add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue']);
|
||||
add_action('wp_ajax_pr_dhl_dismiss_soap_removed_notice', [__CLASS__, 'dismiss']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the migration notice should be shown to the current user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_show()
|
||||
{
|
||||
if (! current_user_can('manage_woocommerce')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The DHL Paket REST flow is Germany-only.
|
||||
if (PR_DHL()->get_base_country() !== 'DE') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Already acknowledged.
|
||||
if (get_option(self::DISMISSED_OPTION) === 'yes') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$settings = get_option('woocommerce_pr_dhl_paket_settings', []);
|
||||
|
||||
// A brand-new store that never configured DHL never used SOAP - nothing to migrate.
|
||||
if (! is_array($settings) || empty($settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only stores that were NOT explicitly on the REST API need to re-check their
|
||||
// credentials. This mirrors the removed API_Utils::is_rest_api_enabled(): an
|
||||
// existing SOAP store has 'dhl_default_api' set to something other than
|
||||
// 'rest-api' (its old default was 'soap'), or unset on very old installs.
|
||||
$selected_api = isset($settings['dhl_default_api']) ? $settings['dhl_default_api'] : '';
|
||||
|
||||
return $selected_api !== 'rest-api';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the admin notice.
|
||||
*/
|
||||
public static function render()
|
||||
{
|
||||
if (! self::should_show()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings_url = admin_url('admin.php?page=wc-settings&tab=shipping§ion=pr_dhl_paket');
|
||||
$guide_url = defined('PR_DHL_PAKET_PARCEL_DE_SHIPPING_API_USER_GUIDE') ? PR_DHL_PAKET_PARCEL_DE_SHIPPING_API_USER_GUIDE : '';
|
||||
?>
|
||||
<div class="notice notice-warning is-dismissible pr-dhl-soap-removed-notice">
|
||||
<p><strong><?php esc_html_e('DHL for WooCommerce: SOAP API support has been removed', 'dhl-for-woocommerce'); ?></strong></p>
|
||||
<p>
|
||||
<?php esc_html_e('DHL has retired its Business Customer Shipping (Geschäftskundenversand) SOAP API. This plugin now uses the DHL Parcel DE Shipping REST API only, and your store has been switched to REST automatically. Please open the DHL settings and confirm your REST API credentials so shipping label creation keeps working.', 'dhl-for-woocommerce'); ?>
|
||||
</p>
|
||||
<p>
|
||||
<a href="<?php echo esc_url($settings_url); ?>" class="button button-primary"><?php esc_html_e('Check DHL settings', 'dhl-for-woocommerce'); ?></a>
|
||||
<?php if ($guide_url !== '') { ?>
|
||||
<a href="<?php echo esc_url($guide_url); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e('REST API setup guide', 'dhl-for-woocommerce'); ?></a>
|
||||
<?php } ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the dismiss handler on admin pages where the notice can appear.
|
||||
*/
|
||||
public static function enqueue()
|
||||
{
|
||||
if (! self::should_show()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'pr-dhl-notice-soap-removed',
|
||||
PR_DHL_PLUGIN_DIR_URL.'/assets/js/pr-dhl-notice-soap-removed.js',
|
||||
['jquery'],
|
||||
PR_DHL_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'pr-dhl-notice-soap-removed',
|
||||
'pr_dhl_soap_removed_notice',
|
||||
[
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce(self::NONCE_ACTION),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist dismissal so the notice does not return.
|
||||
*/
|
||||
public static function dismiss()
|
||||
{
|
||||
check_ajax_referer(self::NONCE_ACTION, 'nonce');
|
||||
|
||||
if (! current_user_can('manage_woocommerce')) {
|
||||
wp_die('', '', ['response' => 403]);
|
||||
}
|
||||
|
||||
update_option(self::DISMISSED_OPTION, 'yes');
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,988 @@
|
|||
<?php
|
||||
|
||||
use PR\DHL\Utils\API_Utils;
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce order metabox for Deutsche Post INTERNETMARKE labels.
|
||||
*
|
||||
* Separate from the DHL Paket metabox — both can appear on the same order page.
|
||||
* Does NOT extend PR_DHL_WC_Order to avoid AJAX action conflicts with Paket.
|
||||
*/
|
||||
if (! class_exists('PR_DHL_WC_Order_Internetmarke')) {
|
||||
|
||||
class PR_DHL_WC_Order_Internetmarke
|
||||
{
|
||||
const METABOX_ID = 'woocommerce-shipment-internetmarke-label';
|
||||
|
||||
const LABEL_ITEMS_META = '_pr_dhl_im_label_items';
|
||||
|
||||
const LABEL_TRACKING_META = '_pr_dhl_im_label_tracking';
|
||||
|
||||
// Records a completed purchase (shopOrderId + document link) the instant the
|
||||
// wallet is charged, before the PDF is downloaded. A retry reads this to recover
|
||||
// the document instead of buying a second stamp — the duplicate-charge guard.
|
||||
const LABEL_VOUCHER_META = '_pr_dhl_im_label_voucher';
|
||||
|
||||
const NONCE_ACTION = 'create-internetmarke-label';
|
||||
|
||||
const NONCE_FIELD = 'pr_dhl_im_label_nonce';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
public function init_hooks()
|
||||
{
|
||||
// Register the metabox after the Paket metabox (priority 20); it is then
|
||||
// force-pinned to the top of the side column via force_metabox_to_top().
|
||||
add_action('add_meta_boxes', [$this, 'add_meta_box'], 21, 2);
|
||||
|
||||
// Save on standard order save.
|
||||
add_action('woocommerce_process_shop_order_meta', [$this, 'save_meta_box'], 0, 2);
|
||||
|
||||
// AJAX handlers — separate actions from Paket to avoid double-firing.
|
||||
add_action('wp_ajax_wc_shipment_internetmarke_gen_label', [$this, 'generate_label_ajax']);
|
||||
add_action('wp_ajax_wc_shipment_internetmarke_delete_label', [$this, 'delete_label_ajax']);
|
||||
add_action('wp_ajax_wc_shipment_internetmarke_download_label', [$this, 'download_label_ajax']);
|
||||
|
||||
// Clean up local label PDF and order meta when an order is deleted.
|
||||
add_action('woocommerce_before_delete_order', [$this, 'cleanup_on_order_delete']);
|
||||
add_action('before_delete_post', [$this, 'cleanup_on_post_delete']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base products shown in the dropdown.
|
||||
*
|
||||
* Derived from the Deutsche Post `ppl_v590_PARTNER` product list — yellow-highlighted
|
||||
* rows only, confirmed by task owner. The yellow subset covers:
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_base_products()
|
||||
{
|
||||
return [
|
||||
// Domestic letter products
|
||||
'11' => 'Kompaktbrief',
|
||||
'21' => 'Großbrief',
|
||||
'31' => 'Maxibrief',
|
||||
'41' => 'Maxibrief bis 2000 g + Zusatzentgelt MBf',
|
||||
'290' => 'Warensendung',
|
||||
'331' => 'Warensendung 2.000 + Gewichtszuschlag',
|
||||
// International letter products
|
||||
'10011' => 'Kompaktbrief Intern. GK',
|
||||
'10051' => 'Großbrief Intern. GK',
|
||||
'10071' => 'Maxibrief Intern. bis 1.000g GK',
|
||||
'10091' => 'Maxibrief Intern. bis 2.000g GK',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Services available per base product (drives checkbox visibility).
|
||||
* Service keys are internal identifiers; display labels use original Deutsche Post naming.
|
||||
*
|
||||
* @return array product-key => string[] service keys
|
||||
*/
|
||||
public static function get_product_services_map()
|
||||
{
|
||||
return [
|
||||
// Domestic — EINSCHREIBEN EINWURF, EINSCHREIBEN, RÜCKSCHEIN
|
||||
'11' => ['einschreiben_einwurf', 'einschreiben', 'rueckschein'],
|
||||
'21' => ['einschreiben_einwurf', 'einschreiben', 'rueckschein'],
|
||||
'31' => ['einschreiben_einwurf', 'einschreiben', 'rueckschein'],
|
||||
'41' => ['einschreiben_einwurf', 'einschreiben', 'rueckschein'],
|
||||
// Warensendung products have no registered-mail variants in the approved list.
|
||||
'290' => [],
|
||||
'331' => [],
|
||||
// International — EINSCHREIBEN only (no EINWURF or RÜCKSCHEIN variants in the approved list).
|
||||
'10011' => ['einschreiben'],
|
||||
'10051' => ['einschreiben'],
|
||||
'10071' => ['einschreiben'],
|
||||
'10091' => ['einschreiben'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service display labels — original Deutsche Post naming, no English translation.
|
||||
*
|
||||
* @return array service-key => display label
|
||||
*/
|
||||
public static function get_service_labels()
|
||||
{
|
||||
return [
|
||||
'einschreiben_einwurf' => 'EINSCHREIBEN EINWURF',
|
||||
'einschreiben' => 'EINSCHREIBEN',
|
||||
'rueckschein' => 'RÜCKSCHEIN',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a product key + selected services to the actual INTERNETMARKE product ID.
|
||||
*
|
||||
* Rules (from approved spreadsheet):
|
||||
* - EINSCHREIBEN EINWURF is mutually exclusive with EINSCHREIBEN.
|
||||
* - RÜCKSCHEIN is only valid together with EINSCHREIBEN (not with EINWURF).
|
||||
*
|
||||
* @param string $product_key Key from get_base_products().
|
||||
* @param string[] $services Selected service keys.
|
||||
* @return int|null INTERNETMARKE product ID, or null if the combination is invalid.
|
||||
*/
|
||||
public static function resolve_product_id($product_key, array $services)
|
||||
{
|
||||
// product-key => variant-key => product_id
|
||||
$map = [
|
||||
'11' => [
|
||||
'none' => 11,
|
||||
'einwurf' => 1012,
|
||||
'einschreiben' => 1017,
|
||||
'rueckschein' => 1018,
|
||||
],
|
||||
'21' => [
|
||||
'none' => 21,
|
||||
'einwurf' => 1022,
|
||||
'einschreiben' => 1027,
|
||||
'rueckschein' => 1028,
|
||||
],
|
||||
'31' => [
|
||||
'none' => 31,
|
||||
'einwurf' => 1032,
|
||||
'einschreiben' => 1037,
|
||||
'rueckschein' => 1038,
|
||||
],
|
||||
'41' => [
|
||||
'none' => 41,
|
||||
'einwurf' => 1042,
|
||||
'einschreiben' => 1047,
|
||||
'rueckschein' => 1048,
|
||||
],
|
||||
'290' => ['none' => 290],
|
||||
'331' => ['none' => 331],
|
||||
'10011' => ['none' => 10011, 'einschreiben' => 11016],
|
||||
'10051' => ['none' => 10051, 'einschreiben' => 11056],
|
||||
'10071' => ['none' => 10071, 'einschreiben' => 11076],
|
||||
'10091' => ['none' => 10091, 'einschreiben' => 11096],
|
||||
];
|
||||
|
||||
if (! isset($map[$product_key])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only honor services allowed for this product; client-side visibility is
|
||||
// not authoritative, so a crafted request cannot force an invalid combination.
|
||||
$allowed = self::get_product_services_map();
|
||||
$services = isset($allowed[$product_key])
|
||||
? array_values(array_intersect($services, $allowed[$product_key]))
|
||||
: [];
|
||||
|
||||
$variants = $map[$product_key];
|
||||
$has_einwurf = in_array('einschreiben_einwurf', $services, true);
|
||||
$has_einschreiben = in_array('einschreiben', $services, true);
|
||||
$has_rueckschein = in_array('rueckschein', $services, true);
|
||||
|
||||
if ($has_einwurf) {
|
||||
return isset($variants['einwurf']) ? $variants['einwurf'] : null;
|
||||
}
|
||||
|
||||
if ($has_einschreiben && $has_rueckschein) {
|
||||
return isset($variants['rueckschein']) ? $variants['rueckschein'] : null;
|
||||
}
|
||||
|
||||
if ($has_einschreiben) {
|
||||
return isset($variants['einschreiben']) ? $variants['einschreiben'] : null;
|
||||
}
|
||||
|
||||
return isset($variants['none']) ? $variants['none'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the INTERNETMARKE metabox on the order page.
|
||||
*
|
||||
* @param string $post_type
|
||||
* @param WP_Post|WC_Order $post_or_order_object
|
||||
*/
|
||||
public function add_meta_box($post_type, $post_or_order_object)
|
||||
{
|
||||
// Internetmarke is only available for German stores.
|
||||
if (WC()->countries->get_base_country() !== 'DE') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide metabox until credentials are saved.
|
||||
$settings = get_option('woocommerce_pr_dhl_paket_settings', []);
|
||||
if (empty($settings['internetmarke_api_user']) || empty($settings['internetmarke_api_password'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order = $this->init_order_object($post_or_order_object);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = API_Utils::is_HPOS() ? wc_get_page_screen_id('shop-order') : 'shop_order';
|
||||
|
||||
add_meta_box(
|
||||
self::METABOX_ID,
|
||||
esc_html__('INTERNETMARKE Label', 'dhl-for-woocommerce'),
|
||||
[$this, 'meta_box'],
|
||||
$screen,
|
||||
'side',
|
||||
'high'
|
||||
);
|
||||
|
||||
// Pin the metabox to the very top of the side column, overriding any
|
||||
// per-user drag order WordPress may have saved for this screen.
|
||||
add_filter('get_user_option_meta-box-order_'.$screen, [$this, 'force_metabox_to_top']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the INTERNETMARKE metabox to the top of the "side" column.
|
||||
*
|
||||
* WordPress stores each user's drag-and-drop metabox order and renders
|
||||
* boxes in that saved order regardless of the priority passed to
|
||||
* add_meta_box(). Filtering the saved order here keeps our metabox pinned
|
||||
* first in the side context on every load.
|
||||
*
|
||||
* @param array|false $order Saved metabox order keyed by context.
|
||||
* @return array
|
||||
*/
|
||||
public function force_metabox_to_top($order)
|
||||
{
|
||||
if (! is_array($order)) {
|
||||
$order = [];
|
||||
}
|
||||
|
||||
// Make sure our box isn't lingering in another context.
|
||||
foreach (['normal', 'advanced'] as $context) {
|
||||
if (! empty($order[$context])) {
|
||||
$ids = array_diff(array_filter(explode(',', $order[$context])), [self::METABOX_ID]);
|
||||
$order[$context] = implode(',', $ids);
|
||||
}
|
||||
}
|
||||
|
||||
$side = isset($order['side']) ? array_filter(explode(',', $order['side'])) : [];
|
||||
$side = array_values(array_diff($side, [self::METABOX_ID]));
|
||||
array_unshift($side, self::METABOX_ID);
|
||||
$order['side'] = implode(',', $side);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the metabox callback parameter to a WC_Order (supports HPOS and legacy).
|
||||
*
|
||||
* @param WP_Post|WC_Order $metabox_object
|
||||
* @return WC_Order|false
|
||||
*/
|
||||
protected function init_order_object($metabox_object)
|
||||
{
|
||||
if (is_a($metabox_object, 'WP_Post')) {
|
||||
return wc_get_order($metabox_object->ID);
|
||||
}
|
||||
|
||||
if (is_a($metabox_object, 'WC_Order')) {
|
||||
return $metabox_object;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Metabox render
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Render the INTERNETMARKE metabox content.
|
||||
*
|
||||
* Follows the same structural pattern as the Paket metabox:
|
||||
* - button HTML is built in PHP and passed via wp_localize_script
|
||||
* - JS uses those strings in the AJAX success handler
|
||||
*
|
||||
* @param WP_Post|WC_Order $post_or_order_object
|
||||
*/
|
||||
public function meta_box($post_or_order_object)
|
||||
{
|
||||
$order = $this->init_order_object($post_or_order_object);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order_id = $order->get_id();
|
||||
|
||||
$label_items = $this->get_label_items($order_id);
|
||||
$label_tracking = $this->get_label_tracking($order_id);
|
||||
|
||||
$selected_product = ! empty($label_items['pr_dhl_im_product'])
|
||||
? $label_items['pr_dhl_im_product']
|
||||
: '11'; // default to Kompaktbrief
|
||||
|
||||
$saved_services = ! empty($label_items['pr_dhl_im_services']) && is_array($label_items['pr_dhl_im_services'])
|
||||
? $label_items['pr_dhl_im_services']
|
||||
: [];
|
||||
|
||||
$has_label = ! empty($label_tracking);
|
||||
|
||||
// A stamp was already bought but its document was never downloaded (a failure
|
||||
// after the charge). The main button then recovers the document instead of
|
||||
// buying again, and its label reflects that so the merchant isn't afraid of a
|
||||
// double charge.
|
||||
$voucher = $this->get_label_voucher($order_id);
|
||||
$is_pending = ! $has_label && ! empty($voucher['shop_order_id']);
|
||||
// Lock the selection once a stamp exists (bought or pending) — changing it can't
|
||||
// alter what has already been paid for; deleting the label unlocks it again.
|
||||
$is_disabled = ($has_label || $is_pending) ? 'disabled' : '';
|
||||
|
||||
$base_products = self::get_base_products();
|
||||
$services_map = self::get_product_services_map();
|
||||
$service_labels = self::get_service_labels();
|
||||
|
||||
$domestic_products = ['11', '21', '31', '41', '290', '331'];
|
||||
$intl_products = ['10011', '10051', '10071', '10091'];
|
||||
|
||||
// Build button HTML in PHP — same pattern as Paket metabox — so the JS
|
||||
// success handler can inject pre-translated strings without string literals in JS.
|
||||
$main_button_label = $is_pending
|
||||
? esc_html__('Retrieve document', 'dhl-for-woocommerce')
|
||||
: esc_html__('Generate label', 'dhl-for-woocommerce');
|
||||
$main_button = '<button id="im-label-button" class="button button-primary">'
|
||||
.$main_button_label
|
||||
.'</button>';
|
||||
|
||||
$label_url = $has_label ? esc_url($this->get_download_label_url($order_id)) : '#';
|
||||
$print_button = '<a href="'.$label_url.'" id="im-label-print" class="button button-primary" download target="_blank">'
|
||||
.esc_html__('Download label', 'dhl-for-woocommerce')
|
||||
.'</a>';
|
||||
|
||||
$delete_label = '<span class="wc_dhl_delete"><a href="#" id="im-delete-label">'
|
||||
.esc_html__('Delete label', 'dhl-for-woocommerce')
|
||||
.'</a></span>';
|
||||
|
||||
// Data passed to JS — mirrors the pattern in pr-dhl.js / dhl_label_data.
|
||||
$im_order_data = [
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce(self::NONCE_ACTION),
|
||||
'order_id' => $order_id,
|
||||
'services_map' => $services_map,
|
||||
'has_label' => $has_label,
|
||||
'ajax_error' => esc_html__('The label request failed or timed out. Please check the order and try again.', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
$im_label_data = [
|
||||
'main_button' => $main_button,
|
||||
'print_button' => $print_button,
|
||||
'delete_label' => $delete_label,
|
||||
];
|
||||
|
||||
?>
|
||||
<div id="shipment-im-label-form">
|
||||
|
||||
<?php wp_nonce_field(self::NONCE_ACTION, self::NONCE_FIELD); ?>
|
||||
|
||||
<div class="shipment-dhl-row-container shipment-im-row-product">
|
||||
<div class="shipment-dhl-icon-container">
|
||||
<span class="shipment-dhl-icon shipment-dhl-icon-service"></span>
|
||||
<?php esc_html_e('Product', 'dhl-for-woocommerce'); ?>
|
||||
</div>
|
||||
|
||||
<p class="form-field">
|
||||
<label for="pr_dhl_im_product"><?php esc_html_e('Product selected:', 'dhl-for-woocommerce'); ?></label>
|
||||
<select id="pr_dhl_im_product" name="pr_dhl_im_product" <?php echo esc_attr($is_disabled); ?>>
|
||||
<optgroup label="<?php esc_attr_e('Domestic', 'dhl-for-woocommerce'); ?>">
|
||||
<?php foreach ($domestic_products as $key) { ?>
|
||||
<?php if (isset($base_products[$key])) { ?>
|
||||
<option value="<?php echo esc_attr($key); ?>" <?php selected($selected_product, $key); ?>>
|
||||
<?php echo esc_html($base_products[$key]); ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</optgroup>
|
||||
<optgroup label="<?php esc_attr_e('International', 'dhl-for-woocommerce'); ?>">
|
||||
<?php foreach ($intl_products as $key) { ?>
|
||||
<?php if (isset($base_products[$key])) { ?>
|
||||
<option value="<?php echo esc_attr($key); ?>" <?php selected($selected_product, $key); ?>>
|
||||
<?php echo esc_html($base_products[$key]); ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</optgroup>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="shipment-dhl-row-container shipment-im-row-services" id="im-services-container">
|
||||
<div class="shipment-dhl-icon-container">
|
||||
<span class="shipment-dhl-icon shipment-dhl-icon-additional-services"></span>
|
||||
<?php esc_html_e('Additional services', 'dhl-for-woocommerce'); ?>
|
||||
</div>
|
||||
|
||||
<?php foreach ($service_labels as $service_key => $service_label) { ?>
|
||||
<?php
|
||||
$available_for_default = in_array($service_key, $services_map[$selected_product] ?? [], true);
|
||||
$checked = in_array($service_key, $saved_services, true);
|
||||
$row_style = $available_for_default ? '' : 'display:none;';
|
||||
|
||||
// RÜCKSCHEIN is further hidden if EINSCHREIBEN is not checked; JS manages this on interaction.
|
||||
if ($service_key === 'rueckschein' && $available_for_default && ! in_array('einschreiben', $saved_services, true)) {
|
||||
$row_style = 'display:none;';
|
||||
}
|
||||
?>
|
||||
<p class="form-field im-service-row im-service-row-<?php echo esc_attr($service_key); ?>"
|
||||
data-service="<?php echo esc_attr($service_key); ?>"
|
||||
style="<?php echo esc_attr($row_style); ?>">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="pr_dhl_im_service_<?php echo esc_attr($service_key); ?>"
|
||||
name="pr_dhl_im_services[]"
|
||||
value="<?php echo esc_attr($service_key); ?>"
|
||||
<?php checked($checked); ?>
|
||||
<?php echo esc_attr($is_disabled); ?>
|
||||
/>
|
||||
<?php echo esc_html($service_label); // Original Deutsche Post name — no English translation.?>
|
||||
</label>
|
||||
</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php if ($has_label) { ?>
|
||||
|
||||
<?php
|
||||
echo $print_button; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
echo ' ';
|
||||
echo $delete_label; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
?>
|
||||
|
||||
<?php if (! empty($label_tracking['tracking_number'])) { ?>
|
||||
<p class="im-tracking-number">
|
||||
<?php
|
||||
echo esc_html__('Tracking number:', 'dhl-for-woocommerce').' ';
|
||||
echo esc_html($label_tracking['tracking_number']);
|
||||
?>
|
||||
</p>
|
||||
<?php } ?>
|
||||
|
||||
<?php } else { ?>
|
||||
|
||||
<?php if ($is_pending) { ?>
|
||||
<p class="im-pending-notice">
|
||||
<?php esc_html_e('A stamp was already purchased for this order but its document was not saved. Retrieving it will not charge you again.', 'dhl-for-woocommerce'); ?>
|
||||
</p>
|
||||
<?php } ?>
|
||||
|
||||
<?php echo $main_button; // phpcs:ignore WordPress.Security.EscapeOutput?>
|
||||
|
||||
<?php if ($is_pending) { ?>
|
||||
<?php echo ' '.$delete_label; // phpcs:ignore WordPress.Security.EscapeOutput?>
|
||||
<?php } ?>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-shipment-im-order-js',
|
||||
PR_DHL_PLUGIN_DIR_URL.'/assets/js/pr-dhl-internetmarke-order.js',
|
||||
['jquery'],
|
||||
PR_DHL_VERSION,
|
||||
true
|
||||
);
|
||||
wp_localize_script('wc-shipment-im-order-js', 'dhl_im_order_data', $im_order_data);
|
||||
wp_localize_script('wc-shipment-im-order-js', 'dhl_im_label_data', $im_label_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist selected product and services when the order is saved via the standard form.
|
||||
*
|
||||
* @param int $post_id
|
||||
* @param WP_Post|null $post
|
||||
*/
|
||||
public function save_meta_box($post_id, $post = null)
|
||||
{
|
||||
if (empty($_POST[self::NONCE_FIELD])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nonce = sanitize_text_field(wp_unslash($_POST[self::NONCE_FIELD]));
|
||||
if (! wp_verify_nonce($nonce, self::NONCE_ACTION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! current_user_can('edit_shop_orders')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = [];
|
||||
|
||||
if (isset($_POST['pr_dhl_im_product'])) {
|
||||
$args['pr_dhl_im_product'] = sanitize_text_field(wp_unslash($_POST['pr_dhl_im_product']));
|
||||
}
|
||||
|
||||
// Services come as a checkbox array or may be absent when none are checked.
|
||||
$args['pr_dhl_im_services'] = [];
|
||||
if (isset($_POST['pr_dhl_im_services']) && is_array($_POST['pr_dhl_im_services'])) {
|
||||
$args['pr_dhl_im_services'] = array_map('sanitize_text_field', wp_unslash($_POST['pr_dhl_im_services']));
|
||||
}
|
||||
|
||||
if (! empty($args)) {
|
||||
$this->save_label_items($post_id, $args);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// AJAX: Generate label
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* AJAX handler: save selections and attempt label generation via the Internetmarke API.
|
||||
* Follows the same error-handling pattern as PR_DHL_WC_Order::save_meta_box_ajax().
|
||||
*/
|
||||
public function generate_label_ajax()
|
||||
{
|
||||
$this->begin_output_capture();
|
||||
|
||||
check_ajax_referer(self::NONCE_ACTION, self::NONCE_FIELD);
|
||||
|
||||
if (! current_user_can('edit_shop_orders')) {
|
||||
$this->send_json_response(['error' => esc_html__('You do not have permission to generate labels.', 'dhl-for-woocommerce')]);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$order_id = isset($_POST['order_id']) ? absint($_POST['order_id']) : 0;
|
||||
|
||||
if (! $order_id) {
|
||||
$this->send_json_response(['error' => esc_html__('Invalid order ID.', 'dhl-for-woocommerce')]);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
// Save form inputs first — same order as Paket's save_meta_box_ajax().
|
||||
$this->save_meta_box($order_id);
|
||||
|
||||
$label_items = $this->get_label_items($order_id);
|
||||
$product_key = isset($label_items['pr_dhl_im_product']) ? $label_items['pr_dhl_im_product'] : '';
|
||||
$services = isset($label_items['pr_dhl_im_services']) && is_array($label_items['pr_dhl_im_services'])
|
||||
? $label_items['pr_dhl_im_services']
|
||||
: [];
|
||||
|
||||
// Resolve product + services to the canonical INTERNETMARKE product ID.
|
||||
$product_id = self::resolve_product_id($product_key, $services);
|
||||
|
||||
if ($product_id === null) {
|
||||
$this->send_json_response(
|
||||
[
|
||||
'error' => esc_html__('Invalid product / service combination. Please check your selection.', 'dhl-for-woocommerce'),
|
||||
]
|
||||
);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
try {
|
||||
$im_api = new PR_DHL_API_Internetmarke;
|
||||
$voucher = $this->get_label_voucher($order_id);
|
||||
|
||||
if (empty($voucher['shop_order_id'])) {
|
||||
// First attempt: buy the stamp (this debits the Portokasse wallet) and
|
||||
// persist the voucher immediately, before downloading, so a later
|
||||
// download failure can never trigger a second charge.
|
||||
$voucher = $im_api->purchase_label($order_id, $product_id);
|
||||
$this->save_label_voucher($order_id, $voucher);
|
||||
} else {
|
||||
// A charge is already recorded for this order. Never purchase again —
|
||||
// recover the (possibly expired) document via the documented read-only
|
||||
// GET endpoint, which returns a fresh link without charging.
|
||||
$voucher = $im_api->retrieve_label($voucher['shop_order_id']);
|
||||
$this->save_label_voucher($order_id, $voucher);
|
||||
}
|
||||
|
||||
if (empty($voucher['remote_link'])) {
|
||||
throw new Exception(esc_html__('The stamp was purchased but INTERNETMARKE returned no document. Click "Retrieve document" to try again — you will not be charged twice.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Download is retry-safe: the purchase is already persisted above.
|
||||
$im_api->download_voucher($voucher['remote_link'], $order_id);
|
||||
|
||||
$this->save_label_tracking(
|
||||
$order_id,
|
||||
[
|
||||
'label_url' => $this->get_download_label_url($order_id),
|
||||
'tracking_number' => isset($voucher['tracking_number']) ? $voucher['tracking_number'] : '',
|
||||
]
|
||||
);
|
||||
|
||||
$this->send_json_response(
|
||||
[
|
||||
'label_url' => esc_url_raw($this->get_download_label_url($order_id)),
|
||||
'tracking_number' => isset($voucher['tracking_number']) ? esc_html($voucher['tracking_number']) : '',
|
||||
'download_msg' => esc_html__('Your INTERNETMARKE label is ready. Click "Download label" to save it.', 'dhl-for-woocommerce'),
|
||||
]
|
||||
);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Domain errors carry safe, translated, user-facing messages.
|
||||
$this->send_json_response(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
// Native PHP errors (TypeError, etc.) can leak internal detail — log the
|
||||
// raw message and return a generic one.
|
||||
PR_DHL()->log_msg('[INTERNETMARKE] Unexpected error generating label for order '.$order_id.': '.$e->getMessage());
|
||||
$this->send_json_response(['error' => esc_html__('The label could not be generated because of an unexpected error. Please check the DHL logs and try again.', 'dhl-for-woocommerce')]);
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler: remove the stored INTERNETMARKE label tracking data.
|
||||
* Follows the same response pattern as PR_DHL_WC_Order::delete_label_ajax().
|
||||
*/
|
||||
public function delete_label_ajax()
|
||||
{
|
||||
$this->begin_output_capture();
|
||||
|
||||
check_ajax_referer(self::NONCE_ACTION, self::NONCE_FIELD);
|
||||
|
||||
if (! current_user_can('edit_shop_orders')) {
|
||||
$this->send_json_response(['error' => esc_html__('You do not have permission to delete labels.', 'dhl-for-woocommerce')]);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$order_id = isset($_POST['order_id']) ? absint($_POST['order_id']) : 0;
|
||||
|
||||
if (! $order_id) {
|
||||
$this->send_json_response(['error' => esc_html__('Invalid order ID.', 'dhl-for-woocommerce')]);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$this->delete_label_tracking($order_id);
|
||||
$this->delete_label_voucher($order_id);
|
||||
$this->delete_label_file($order_id);
|
||||
|
||||
$this->send_json_response(
|
||||
[
|
||||
'button_txt' => esc_html__('Generate label', 'dhl-for-woocommerce'),
|
||||
]
|
||||
);
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON AJAX response, discarding any stray output first.
|
||||
*
|
||||
* PHP notices/warnings, or wpdb database-error prints emitted when a site
|
||||
* runs with WP_DEBUG_DISPLAY enabled, would otherwise be written to the
|
||||
* response body before the JSON — breaking JSON parsing on the client and
|
||||
* making the label request appear to fail even though it succeeded. The
|
||||
* matching ob_start() at the top of each handler captures that output; here
|
||||
* we drop it so the body is always valid JSON, whatever the site's debug config.
|
||||
*
|
||||
* @param mixed $data Response payload passed straight to wp_send_json().
|
||||
*/
|
||||
protected function send_json_response($data)
|
||||
{
|
||||
$this->discard_captured_output();
|
||||
|
||||
wp_send_json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested output-buffer level present when the current AJAX handler started.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $entry_ob_level = 0;
|
||||
|
||||
/**
|
||||
* Start capturing stray output, remembering the pre-existing buffer level so we
|
||||
* only ever discard our own buffers, never those owned by core or other plugins.
|
||||
*/
|
||||
protected function begin_output_capture()
|
||||
{
|
||||
$this->entry_ob_level = ob_get_level();
|
||||
ob_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard only the buffers opened by this handler, leaving any buffer that existed
|
||||
* beforehand intact so we don't swallow output (or errors) captured elsewhere.
|
||||
*/
|
||||
protected function discard_captured_output()
|
||||
{
|
||||
while (ob_get_level() > $this->entry_ob_level) {
|
||||
ob_end_clean();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Order meta helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Merge and save label field values to order meta.
|
||||
*
|
||||
* @param int $order_id
|
||||
*/
|
||||
protected function save_label_items($order_id, array $items)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = $order->get_meta(self::LABEL_ITEMS_META);
|
||||
if (is_array($existing)) {
|
||||
$items = array_merge($existing, $items);
|
||||
}
|
||||
|
||||
$order->update_meta_data(self::LABEL_ITEMS_META, $items);
|
||||
$order->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve saved label field values from order meta.
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return array
|
||||
*/
|
||||
public function get_label_items($order_id)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = $order->get_meta(self::LABEL_ITEMS_META);
|
||||
|
||||
return is_array($items) ? $items : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save label tracking data (tracking number, label URL, etc.) to order meta.
|
||||
*
|
||||
* @param int $order_id
|
||||
*/
|
||||
public function save_label_tracking($order_id, array $tracking_info)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order->update_meta_data(self::LABEL_TRACKING_META, $tracking_info);
|
||||
$order->save_meta_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve stored label tracking data.
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return array
|
||||
*/
|
||||
public function get_label_tracking($order_id)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tracking = $order->get_meta(self::LABEL_TRACKING_META);
|
||||
|
||||
return is_array($tracking) ? $tracking : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove label tracking data so the metabox reverts to its initial state.
|
||||
*
|
||||
* @param int $order_id
|
||||
*/
|
||||
public function delete_label_tracking($order_id)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order->delete_meta_data(self::LABEL_TRACKING_META);
|
||||
$order->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the recorded purchase voucher (shopOrderId, document link, tracking).
|
||||
*
|
||||
* Present as soon as the wallet is charged, before the PDF is downloaded, so a
|
||||
* retry can recover the document instead of buying a second stamp.
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return array { shop_order_id: string, remote_link: string, tracking_number: string }
|
||||
*/
|
||||
public function get_label_voucher($order_id)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$voucher = $order->get_meta(self::LABEL_VOUCHER_META);
|
||||
|
||||
return is_array($voucher) ? $voucher : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the purchase voucher the moment the stamp is bought.
|
||||
*
|
||||
* @param int $order_id
|
||||
* @param array $voucher { shop_order_id, remote_link, tracking_number }
|
||||
*/
|
||||
public function save_label_voucher($order_id, array $voucher)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order->update_meta_data(self::LABEL_VOUCHER_META, $voucher);
|
||||
$order->save_meta_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the recorded purchase voucher (on delete, so a fresh purchase can start).
|
||||
*
|
||||
* @param int $order_id
|
||||
*/
|
||||
public function delete_label_voucher($order_id)
|
||||
{
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order->delete_meta_data(self::LABEL_VOUCHER_META);
|
||||
$order->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the local label PDF when a HPOS order is deleted. The order meta is
|
||||
* removed together with the order, so only the file needs cleaning up here.
|
||||
*
|
||||
* @param int $order_id
|
||||
*/
|
||||
public function cleanup_on_order_delete($order_id)
|
||||
{
|
||||
$this->delete_label_file($order_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the local label PDF when a legacy post-based order is deleted. The order
|
||||
* meta is removed together with the post, so only the file needs cleaning up here.
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public function cleanup_on_post_delete($post_id)
|
||||
{
|
||||
if (get_post_type($post_id) !== 'shop_order') {
|
||||
return;
|
||||
}
|
||||
$this->delete_label_file($post_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the locally-stored label PDF for an order (if it exists).
|
||||
*
|
||||
* @param int $order_id
|
||||
*/
|
||||
protected function delete_label_file($order_id)
|
||||
{
|
||||
$file_path = $this->get_label_file_path($order_id);
|
||||
|
||||
if (file_exists($file_path)) {
|
||||
wp_delete_file($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute path to the locally-stored label PDF for an order.
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return string
|
||||
*/
|
||||
protected function get_label_file_path($order_id)
|
||||
{
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
return $upload_dir['basedir'].'/woocommerce_dhl_label/dhl-im-label-'.(int) $order_id.'.pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a protected admin-ajax URL that streams the label PDF.
|
||||
*
|
||||
* The label folder is protected by `deny from all`, so the raw uploads URL
|
||||
* returns 403. Serving the file via readfile() through this endpoint bypasses
|
||||
* that — the same approach as the Paket download endpoint.
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return string
|
||||
*/
|
||||
protected function get_download_label_url($order_id)
|
||||
{
|
||||
return add_query_arg(
|
||||
[
|
||||
'action' => 'wc_shipment_internetmarke_download_label',
|
||||
'order_id' => (int) $order_id,
|
||||
'nonce' => wp_create_nonce('download-internetmarke-label-'.(int) $order_id),
|
||||
],
|
||||
admin_url('admin-ajax.php')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler: stream the stored label PDF to an authorised user.
|
||||
*
|
||||
* Bypasses the label folder's `deny from all` protection by reading the file
|
||||
* server-side after verifying the nonce and capability.
|
||||
*/
|
||||
public function download_label_ajax()
|
||||
{
|
||||
// Capture stray output so it cannot corrupt the PDF binary or send
|
||||
// premature headers; discarded before the file is streamed below.
|
||||
$this->begin_output_capture();
|
||||
|
||||
$order_id = isset($_GET['order_id']) ? absint($_GET['order_id']) : 0;
|
||||
$nonce = isset($_GET['nonce']) ? sanitize_text_field(wp_unslash($_GET['nonce'])) : '';
|
||||
|
||||
if (! $order_id || ! wp_verify_nonce($nonce, 'download-internetmarke-label-'.$order_id)) {
|
||||
wp_die(esc_html__('Invalid or expired label download link.', 'dhl-for-woocommerce'), '', ['response' => 403]);
|
||||
}
|
||||
|
||||
if (! current_user_can('edit_shop_orders')) {
|
||||
wp_die(esc_html__('You do not have permission to download labels.', 'dhl-for-woocommerce'), '', ['response' => 403]);
|
||||
}
|
||||
|
||||
$file_path = $this->get_label_file_path($order_id);
|
||||
|
||||
if (! file_exists($file_path)) {
|
||||
wp_die(esc_html__('Label file not found.', 'dhl-for-woocommerce'), '', ['response' => 404]);
|
||||
}
|
||||
|
||||
$this->discard_captured_output();
|
||||
|
||||
nocache_headers();
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: inline; filename="dhl-im-label-'.$order_id.'.pdf"');
|
||||
header('Content-Length: '.filesize($file_path));
|
||||
readfile($file_path); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1605
dev/dhl-for-woocommerce/includes/class-pr-dhl-wc-order-paket.php
Normal file
1605
dev/dhl-for-woocommerce/includes/class-pr-dhl-wc-order-paket.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce DHL Shipping Product.
|
||||
*
|
||||
* @category Product
|
||||
*
|
||||
* @author Shadi Manna
|
||||
*/
|
||||
if (! class_exists('PR_DHL_WC_Product_Deutsche_Post')) {
|
||||
|
||||
class PR_DHL_WC_Product_Deutsche_Post extends PR_DHL_WC_Product
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->manufacture_country_label = esc_html__('Country of origin (Deutsche Post International)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_label = esc_html__('Harmonized Tariff code (Deutsche Post International)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_description = esc_html__('Optional information for non-EU shipments. Appears on CN22 (Deutsche Post International).', 'dhl-for-woocommerce');
|
||||
}
|
||||
|
||||
public function get_manufacture_tooltip()
|
||||
{
|
||||
return esc_html__('Country of origin of goods. Mandatory for all non-EU shipments. Appears on CN22 (Deutsche Post International).', 'dhl-for-woocommerce');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the meta box for shipment info on the order page
|
||||
*/
|
||||
public function additional_product_settings()
|
||||
{
|
||||
|
||||
$content_indicators = ['0' => esc_html__('- select content indicator -', 'dhl-for-woocommerce')];
|
||||
|
||||
try {
|
||||
|
||||
$dhl_obj = PR_DHL()->get_dhl_factory();
|
||||
|
||||
$content_indicators += $dhl_obj->get_dhl_content_indicator();
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
echo '<p class="wc_dhl_error">'.esc_html($e->getMessage()).'</p>';
|
||||
}
|
||||
/*
|
||||
woocommerce_wp_select(
|
||||
array(
|
||||
'id' => '_dhl_dangerous_goods',
|
||||
'label' => esc_html__( 'Content Indicator (DHL)', 'dhl-for-woocommerce' ),
|
||||
'description' => esc_html__( 'The content indicator is used as dangerous goods classifier required for air transport.', 'dhl-for-woocommerce' ),
|
||||
'desc_tip' => 'true',
|
||||
//'value' => $country_value,
|
||||
'options' => $content_indicators
|
||||
)
|
||||
);
|
||||
*/
|
||||
woocommerce_wp_textarea_input(
|
||||
[
|
||||
'id' => '_dhl_export_description',
|
||||
'label' => esc_html__('Content description (Deutsche Post International)', 'dhl-for-woocommerce'),
|
||||
'description' => esc_html__('Description of goods (max 33 characters). Mandatory for all non-EU shipments. Appears on CN22 (Deutsche Post International).', 'dhl-for-woocommerce'),
|
||||
'desc_tip' => 'true',
|
||||
'custom_attributes' => ['maxlength' => '50'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function save_additional_product_settings($post_id)
|
||||
{
|
||||
|
||||
// Country of manufacture
|
||||
/*
|
||||
if ( isset( $_POST['_dhl_dangerous_goods'] ) ) {
|
||||
update_post_meta( $post_id, '_dhl_dangerous_goods', wc_clean( $_POST['_dhl_dangerous_goods'] ) );
|
||||
}*/
|
||||
|
||||
// HS code value
|
||||
if (isset($_POST['_dhl_export_description'])) {
|
||||
update_post_meta($post_id, '_dhl_export_description', wc_clean($_POST['_dhl_export_description']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PR_DHL_WC_Product_Editor_Deutsche_Post class file.
|
||||
* Adds Deutsche Post product fields.
|
||||
*/
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (! class_exists('PR_DHL_WC_Product_Editor_Deutsche_Post')) {
|
||||
/**
|
||||
* PR_DHL_WC_Product_Editor_Deutsche_Post class.
|
||||
*/
|
||||
class PR_DHL_WC_Product_Editor_Deutsche_Post extends PR_DHL_WC_Product_Editor
|
||||
{
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->manufacture_tooltip = esc_html__('Country of origin of goods. Mandatory for all non-EU shipments. Appears on CN22 (Deutsche Post International).', 'dhl-for-woocommerce');
|
||||
$this->manufacture_country_label = esc_html__('Country of origin (Deutsche Post International)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_label = esc_html__('Harmonized Tariff code (Deutsche Post International)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_description = esc_html__('Optional information for non-EU shipments. Appears on CN22 (Deutsche Post International).', 'dhl-for-woocommerce');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional product fields.
|
||||
*/
|
||||
public function additional_product_fields($parent): void
|
||||
{
|
||||
// Add shipment info on the order page Checkbox Block.
|
||||
$parent->add_block(
|
||||
[
|
||||
'id' => '_dhl_export_description',
|
||||
'blockName' => 'woocommerce/product-text-area-field',
|
||||
'attributes' => [
|
||||
'label' => __('Content description (Deutsche Post International)', 'dhl-for-woocommerce'),
|
||||
'property' => 'meta_data._dhl_export_description',
|
||||
'tooltip' => __('Description of goods (max 33 characters). Mandatory for all non-EU shipments. Appears on CN22 (Deutsche Post International).', 'dhl-for-woocommerce'),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PR_DHL_WC_Product_Editor_Paket class file.
|
||||
* Adds DHL Paket product fields.
|
||||
*/
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (! class_exists('PR_DHL_WC_Product_Paket_Editor')) {
|
||||
/**
|
||||
* PR_DHL_WC_Product_Editor_Paket class.
|
||||
*/
|
||||
class PR_DHL_WC_Product_Editor_Paket extends PR_DHL_WC_Product_Editor
|
||||
{
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->manufacture_tooltip = esc_html__('Country of Manufacture', 'dhl-for-woocommerce');
|
||||
$this->manufacture_country_label = esc_html__('Country of Manufacture (DHL)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_label = esc_html__('Harmonized Tariff Schedule (DHL)', 'dhl-for-woocommerce');
|
||||
$this->hs_code_description = sprintf(
|
||||
/* translators: %s is the HTSUS lookup tool URL */
|
||||
esc_html__('Harmonized Tariff Schedule is a number assigned to every possible commodity that can be imported or exported from any country. For shipments to the United States, enter the full 10-digit HTSUS code — look it up at %s', 'dhl-for-woocommerce'),
|
||||
'https://hts.usitc.gov/'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional product fields.
|
||||
*/
|
||||
public function additional_product_fields($parent): void
|
||||
{
|
||||
// Add shipment info on the order page Checkbox Block.
|
||||
$parent->add_block(
|
||||
[
|
||||
'id' => '_dhl_no_same_day_transfer',
|
||||
'blockName' => 'woocommerce/product-checkbox-field',
|
||||
'attributes' => [
|
||||
'label' => __('Cannot Transfer On Day Of Order (DHL)', 'dhl-for-woocommerce'),
|
||||
'property' => 'meta_data._dhl_no_same_day_transfer',
|
||||
'tooltip' => __('This product cannot be transfered to DHL on the same day of the order. Checking this disables preferred services on the checkout page.', 'dhl-for-woocommerce'),
|
||||
'checkedValue' => 'yes',
|
||||
'uncheckedValue' => '',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce DHL Shipping Order.
|
||||
*
|
||||
* @category Product
|
||||
*
|
||||
* @author Shadi Manna
|
||||
*/
|
||||
if (! class_exists('PR_DHL_WC_Product_Paket')) {
|
||||
|
||||
class PR_DHL_WC_Product_Paket extends PR_DHL_WC_Product
|
||||
{
|
||||
public function get_manufacture_tooltip()
|
||||
{
|
||||
return esc_html__('Country of Manufacture', 'dhl-for-woocommerce');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the meta box for shipment info on the order page
|
||||
*/
|
||||
public function additional_product_settings()
|
||||
{
|
||||
woocommerce_wp_checkbox(
|
||||
[
|
||||
'id' => '_dhl_no_same_day_transfer',
|
||||
'label' => esc_html__('Cannot Transfer On Day Of Order (DHL)', 'dhl-for-woocommerce'),
|
||||
'description' => esc_html__('This product cannot be transfered to DHL on the same day of the order. Checking this disables preferred services on the checkout page.', 'dhl-for-woocommerce'),
|
||||
// 'desc_tip' => 'false',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function save_additional_product_settings($post_id)
|
||||
{
|
||||
|
||||
// If the same day checkbox is set then it is 'yes', if it is not set (i.e. not sent via $_POST), then it is 'no' unchecked!
|
||||
if (isset($_POST['_dhl_no_same_day_transfer'])) {
|
||||
update_post_meta($post_id, '_dhl_no_same_day_transfer', wc_clean($_POST['_dhl_no_same_day_transfer']));
|
||||
} else {
|
||||
update_post_meta($post_id, '_dhl_no_same_day_transfer', 'no');
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
use PR\DHL\Utils\API_Utils;
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce DHL Wizard.
|
||||
*
|
||||
* @category Admin
|
||||
*
|
||||
* @author Shadi Manna
|
||||
*/
|
||||
if (! class_exists('PR_DHL_WC_Wizard_Paket')) {
|
||||
|
||||
class PR_DHL_WC_Wizard_Paket
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
add_action('admin_footer', [$this, 'display_wizard'], 10);
|
||||
add_action('admin_notices', [$this, 'wizard_notice']);
|
||||
}
|
||||
|
||||
public function wizard_notice()
|
||||
{
|
||||
$screen = get_current_screen();
|
||||
$screen_id = $screen ? $screen->id : '';
|
||||
|
||||
if ($screen_id !== 'woocommerce_page_wc-settings') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($_GET['section']) || $_GET['section'] != 'pr_dhl_paket') {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="updated notice is-dismissible">
|
||||
<p><?php esc_html_e('Welcome to DHL plugin! You\'re almost there, but we think this wizard might help you setup the plugin.', 'dhl-for-woocommerce'); ?></p>
|
||||
<p><a href="#" id="pr-dhl-open-wizard-button" class="button button-primary"><?php esc_html_e('Run wizard', 'dhl-for-woocommerce'); ?></a> <a href="javascript:window.location.reload()" class="button"><?php esc_html_e('dismiss', 'dhl-for-woocommerce'); ?></a></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function display_wizard()
|
||||
{
|
||||
if (! API_Utils::is_new_merchant()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
$screen_id = $screen ? $screen->id : '';
|
||||
|
||||
if ($screen_id !== 'woocommerce_page_wc-settings') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($_GET['section']) || $_GET['section'] != 'pr_dhl_paket') {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="pr-dhl-wc-wizard-overlay">
|
||||
<div class="pr-dhl-wc-wizard-container">
|
||||
<div class="pr-dhl-wc-wizard">
|
||||
<div class="wizard-header">
|
||||
<img src="<?php echo esc_url(PR_DHL_PLUGIN_DIR_URL.'/assets/img/dhl-official.png'); ?>" width="300" class="dhl-logo" alt="DHL Logo" />
|
||||
</div>
|
||||
<form class="wizard" id="dhlStepsWizard">
|
||||
<aside class="wizard-content container">
|
||||
<div class="wizard-step" data-title="Step 1">
|
||||
<h4 class="wizard-title"><?php esc_html_e('Quick Set Up', 'dhl-for-woocommerce'); ?></h4>
|
||||
<div class="wizard-description">
|
||||
<?php esc_html_e('Thank you for installing this plugin. We\'ll guide you through the set up and minimum required fields.', 'dhl-for-woocommerce'); ?>
|
||||
</div>
|
||||
<input type="hidden" name="start_wizard" value="yes" />
|
||||
<div class="form-group">
|
||||
<button class="button-next"><?php esc_html_e('Begin Setup', 'dhl-for-woocommerce'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step" data-title="Step 2">
|
||||
<h4 class="wizard-title"><?php esc_html_e('Account Number (EKP)', 'dhl-for-woocommerce'); ?></h4>
|
||||
<div class="wizard-description">
|
||||
<?php esc_html_e('Your DHL account number (10 digits - numerical), also called "EKP". This will be provided by your local DHL sales organization.', 'dhl-for-woocommerce'); ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_account_num" class="form-control required wizard-dhl-field" id="wizard_dhl_account_num" placeholder="<?php esc_html_e('Enter your EKP', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="button-next"><?php esc_html_e('Next', 'dhl-for-woocommerce'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step" data-title="Step 3">
|
||||
<h4 class="wizard-title"><?php esc_html_e('API Settings', 'dhl-for-woocommerce'); ?></h4>
|
||||
<div class="wizard-description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %1$s & %2$s: link to DHL business portal */
|
||||
esc_html__('Please configure your access to the DHL Paket APIs by means of authentication. Your username for the DHL business customer portal is required. Please note the lowercase letters and test your access data in advance at %1$shere%2$s.', 'dhl-for-woocommerce'),
|
||||
'<a href="'.PR_DHL_PAKET_BUSSINESS_PORTAL.'" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_api_user" class="form-control required wizard-dhl-field" id="wizard_dhl_api_user" placeholder="<?php esc_html_e('Username', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" name="dhl_api_pwd" class="form-control required wizard-dhl-field" id="wizard_dhl_api_pwd" placeholder="<?php esc_html_e('Password', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="button-next"><?php esc_html_e('Next', 'dhl-for-woocommerce'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step" data-title="Step 4">
|
||||
<h4 class="wizard-title"><?php esc_html_e('Participation Number', 'dhl-for-woocommerce'); ?></h4>
|
||||
<div class="wizard-description">
|
||||
<?php esc_html_e('The participation number consists of the last two characters of the respective accounting number, which you will find in your DHL contract data (for example, 01).', 'dhl-for-woocommerce'); ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_participation_V01PAK" class="form-control required wizard-dhl-field" id="wizard_dhl_participation_V01PAK" placeholder="<?php esc_html_e('Regular product', 'dhl-for-woocommerce'); ?>" />
|
||||
<input type="hidden" name="dhl_participation_V01PRIO" class="form-control wizard-dhl-field participation-field" id="wizard_dhl_participation_V01PRIO" />
|
||||
<input type="hidden" name="dhl_participation_V55PAK" class="form-control wizard-dhl-field participation-field" id="wizard_dhl_participation_V55PAK" />
|
||||
<input type="hidden" name="dhl_participation_V54EPAK" class="form-control wizard-dhl-field participation-field" id="wizard_dhl_participation_V54EPAK" />
|
||||
<input type="hidden" name="dhl_participation_V53WPAK" class="form-control wizard-dhl-field participation-field" id="wizard_dhl_participation_V53WPAK" />
|
||||
<input type="hidden" name="dhl_participation_V66WPI" class="form-control wizard-dhl-field participation-field" id="wizard_dhl_participation_V66WPI" />
|
||||
<input type="hidden" name="dhl_participation_return" class="form-control wizard-dhl-field participation-field" id="wizard_dhl_participation_return" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="button-next"><?php esc_html_e('Next', 'dhl-for-woocommerce'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step" data-title="Step 5">
|
||||
<h4 class="wizard-title"><?php esc_html_e('Shipper Address', 'dhl-for-woocommerce'); ?></h4>
|
||||
<div class="wizard-description">
|
||||
<?php esc_html_e('Enter Shipper Address. This address is also used for Pickup Requests.', 'dhl-for-woocommerce'); ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_name" class="form-control required wizard-dhl-field" id="wizard_dhl_shipper_name" placeholder="<?php esc_html_e('Name', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_company" class="form-control required wizard-dhl-field" id="wizard_dhl_shipper_company" placeholder="<?php esc_html_e('Company', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_address" class="form-control required wizard-dhl-field" id="wizard_dhl_shipper_address" placeholder="<?php esc_html_e('Street Address', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_address_no" class="form-control required wizard-dhl-field" id="wizard_dhl_shipper_address_no" placeholder="<?php esc_html_e('Street Address Number', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_address_city" class="form-control required wizard-dhl-field" id="wizard_dhl_shipper_address_city" placeholder="<?php esc_html_e('City', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_address_state" class="form-control required wizard-dhl-field" id="wizard_dhl_shipper_address_state" placeholder="<?php esc_html_e('State', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_address_zip" class="form-control required wizard-dhl-field" id="wizard_dhl_shipper_address_zip" placeholder="<?php esc_html_e('Postcode', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_phone" class="form-control wizard-dhl-field" id="wizard_dhl_shipper_phone" placeholder="<?php esc_html_e('Phone', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="dhl_shipper_email" class="form-control wizard-dhl-field" id="wizard_dhl_email" placeholder="<?php esc_html_e('Email', 'dhl-for-woocommerce'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="button-next"><?php esc_html_e('Next', 'dhl-for-woocommerce'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step" data-title="Step 6">
|
||||
<h4 class="wizard-title"><?php esc_html_e('All set', 'dhl-for-woocommerce'); ?></h4>
|
||||
<div class="wizard-description">
|
||||
<?php esc_html_e('You can find all additional settings under WooCommerce > Settings > Shipping > DHL Paket', 'dhl-for-woocommerce'); ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="hidden" name="dhl_participation_finish" class="form-control wizard-dhl-field participation-field" id="wizard_dhl_participation_finish" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="button-finish"><?php esc_html_e('Finish Setup', 'dhl-for-woocommerce'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</form>
|
||||
</div>
|
||||
<a href="#" class="pr-dhl-wc-skip-wizard"><span class="dashicons dashicons-no"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,380 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
abstract class PR_DHL_API_REST
|
||||
{
|
||||
const WP_POST_TIMEOUT = 30;
|
||||
|
||||
/**
|
||||
* The request endpoint
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = '';
|
||||
|
||||
/**
|
||||
* The query string
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $query = [];
|
||||
|
||||
/**
|
||||
* The request response
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $response = null;
|
||||
|
||||
/**
|
||||
* @var PR_DHL_API_Auth_REST
|
||||
*/
|
||||
protected $dhl_rest_auth;
|
||||
|
||||
/**
|
||||
* @var Integrater
|
||||
*/
|
||||
protected $id = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $token_bearer = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $remote_header = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $query_string = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $body_request = [];
|
||||
|
||||
/**
|
||||
* DHL_Api constructor.
|
||||
*
|
||||
* @param string $api_key, $api_secret
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
$this->dhl_rest_auth = PR_DHL_API_Auth_REST::get_instance();
|
||||
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set id
|
||||
*/
|
||||
public function set_id($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id
|
||||
*
|
||||
* @return $id
|
||||
*/
|
||||
public function get_id()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set endpoint
|
||||
*/
|
||||
protected function set_endpoint($endpoint)
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_endpoint()
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function get_query()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
*/
|
||||
protected function set_query($query)
|
||||
{
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_response()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the response
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function clear_response()
|
||||
{
|
||||
$this->response = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_access_token($client_id, $client_secret)
|
||||
{
|
||||
$this->token_bearer = $this->dhl_rest_auth->get_access_token($client_id, $client_secret);
|
||||
|
||||
return $this->token_bearer;
|
||||
}
|
||||
|
||||
public function delete_access_token()
|
||||
{
|
||||
$this->dhl_rest_auth->delete_access_token();
|
||||
}
|
||||
|
||||
public function post_request($client_id, $client_secret)
|
||||
{
|
||||
$this->set_header($this->get_access_token($client_id, $client_secret));
|
||||
|
||||
$wp_request_url = PR_DHL()->get_api_url().$this->get_endpoint().'?'.$this->get_query_string();
|
||||
$wp_request_headers = $this->get_header();
|
||||
$this->set_message();
|
||||
|
||||
$wp_request_body = $this->get_message();
|
||||
|
||||
PR_DHL()->log_msg('POST URL: '.$wp_request_url);
|
||||
// PR_DHL()->log_msg( 'POST Header: ' . print_r( $wp_request_headers, true ) );
|
||||
PR_DHL()->log_msg('POST Body: '.$wp_request_body);
|
||||
|
||||
$wp_dhl_rest_response = wp_remote_post(
|
||||
$wp_request_url,
|
||||
[
|
||||
'headers' => $wp_request_headers,
|
||||
'body' => $wp_request_body,
|
||||
'timeout' => self::WP_POST_TIMEOUT,
|
||||
]
|
||||
);
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($wp_dhl_rest_response);
|
||||
$response_body = json_decode(wp_remote_retrieve_body($wp_dhl_rest_response));
|
||||
$session_id = wp_remote_retrieve_header($wp_dhl_rest_response, 'x-correlationid');
|
||||
|
||||
PR_DHL()->log_msg('POST Response Header Session ID: '.$session_id);
|
||||
PR_DHL()->log_msg('POST Response Code: '.$response_code);
|
||||
PR_DHL()->log_msg('POST Response Body: '.print_r($response_body, true));
|
||||
|
||||
switch ($response_code) {
|
||||
case '201':
|
||||
break;
|
||||
case '400':
|
||||
$error_message = str_replace('/', ' / ', $response_body->message);
|
||||
|
||||
if (isset($response_body->backendError->message)) {
|
||||
$error_message .= ' '.$response_body->backendError->message;
|
||||
}
|
||||
|
||||
throw new Exception(esc_html__('400 - ', 'dhl-for-woocommerce').esc_html($error_message));
|
||||
break;
|
||||
case '401':
|
||||
throw new Exception(esc_html__('401 - Unauthorized Access - Invalid token or Authentication Header parameter', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '408':
|
||||
throw new Exception(esc_html__('408 - Request Timeout', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '429':
|
||||
throw new Exception(esc_html__('429 - Too many requests in given amount of time', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '503':
|
||||
throw new Exception(esc_html__('503 - Service Unavailable', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
default:
|
||||
if (empty($response_body->message)) {
|
||||
$error_message = esc_html__('POST error or timeout occured. Please try again later.', 'dhl-for-woocommerce');
|
||||
} else {
|
||||
$error_message = str_replace('/', ' / ', $response_body->message);
|
||||
}
|
||||
|
||||
PR_DHL()->log_msg('POST Error: '.$response_code.' - '.$error_message);
|
||||
|
||||
throw new Exception(esc_html($response_code).' - '.esc_html($error_message));
|
||||
break;
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
public function get_request()
|
||||
{
|
||||
$rest_auth = '';
|
||||
$api_url = PR_DHL()->get_api_url();
|
||||
if (is_array($api_url)) {
|
||||
$rest_auth = $this->get_basic_auth_encode($api_url['user'], $api_url['password']);
|
||||
$api_url = $api_url['auth_url'];
|
||||
}
|
||||
|
||||
$this->set_header($rest_auth);
|
||||
|
||||
$wp_request_url = $api_url.$this->get_endpoint().'?'.$this->get_query_string();
|
||||
$wp_request_headers = $this->get_header();
|
||||
|
||||
PR_DHL()->log_msg('GET URL: '.$wp_request_url);
|
||||
|
||||
$wp_dhl_rest_response = wp_remote_get(
|
||||
$wp_request_url,
|
||||
[
|
||||
'headers' => $wp_request_headers,
|
||||
'timeout' => self::WP_POST_TIMEOUT,
|
||||
]
|
||||
);
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($wp_dhl_rest_response);
|
||||
$response_body = json_decode(wp_remote_retrieve_body($wp_dhl_rest_response));
|
||||
|
||||
PR_DHL()->log_msg('GET Response Code: '.$response_code);
|
||||
PR_DHL()->log_msg('GET Response Body: '.print_r($response_body, true));
|
||||
|
||||
switch ($response_code) {
|
||||
case '200':
|
||||
case '201':
|
||||
break;
|
||||
case '400':
|
||||
$error_message = ! empty($response_body->message) ? str_replace('/', ' / ', $response_body->message) : esc_html__('Unknown Error!', 'dhl-for-woocommerce');
|
||||
throw new Exception(esc_html__('400 - ', 'dhl-for-woocommerce').esc_html($error_message));
|
||||
break;
|
||||
case '401':
|
||||
throw new Exception(esc_html__('401 - Unauthorized Access - Invalid token or Authentication Header parameter', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '408':
|
||||
throw new Exception(esc_html__('408 - Request Timeout', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '429':
|
||||
throw new Exception(esc_html__('429 - Too many requests in given amount of time', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '503':
|
||||
throw new Exception(esc_html__('503 - Service Unavailable', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
default:
|
||||
if (empty($response_body->message)) {
|
||||
$error_message = esc_html__('GET error or timeout occured. Please try again later.', 'dhl-for-woocommerce');
|
||||
} else {
|
||||
$error_message = str_replace('/', ' / ', $response_body->message);
|
||||
}
|
||||
|
||||
PR_DHL()->log_msg('GET Error: '.$response_code.' - '.$error_message);
|
||||
|
||||
throw new Exception(esc_html($response_code).' - '.esc_html($error_message));
|
||||
break;
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
protected function get_basic_auth_encode($user, $pass)
|
||||
{
|
||||
return 'Basic '.base64_encode($user.':'.$pass);
|
||||
}
|
||||
|
||||
protected function set_header($authorization = '')
|
||||
{
|
||||
$wp_version = get_bloginfo('version');
|
||||
|
||||
$dhl_header['Content-Type'] = 'application/json';
|
||||
$dhl_header['Accept'] = 'application/json';
|
||||
$dhl_header['Authorization'] = 'Bearer '.$authorization;
|
||||
$dhl_header['User-Agent'] = 'WooCommerce/'.WC_VERSION.' (WordPress/'.$wp_version.') DHL-plug-in/'.PR_DHL_VERSION;
|
||||
|
||||
$this->remote_header = array_merge($this->remote_header, $dhl_header);
|
||||
}
|
||||
|
||||
protected function get_header()
|
||||
{
|
||||
return $this->remote_header;
|
||||
}
|
||||
|
||||
abstract protected function set_query_string();
|
||||
|
||||
protected function get_query_string()
|
||||
{
|
||||
return $this->query_string;
|
||||
}
|
||||
|
||||
protected function set_message() {}
|
||||
|
||||
protected function get_message()
|
||||
{
|
||||
return $this->body_request;
|
||||
}
|
||||
|
||||
protected function validate_field($key, $value)
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
switch ($key) {
|
||||
case 'pickup':
|
||||
$this->validate($value, 'string', 5, 10);
|
||||
break;
|
||||
case 'distribution':
|
||||
$this->validate($value, 'string', 6, 6);
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function validate($value, $type = 'int', $min_len = 0, $max_len = 0)
|
||||
{
|
||||
|
||||
switch ($type) {
|
||||
case 'string':
|
||||
if ((strlen($value) < $min_len) || (strlen($value) > $max_len)) {
|
||||
if ($min_len == $max_len) {
|
||||
/* translators: %s is the required number of characters */
|
||||
throw new Exception(sprintf(esc_html__('The value must be %s characters.', 'dhl-for-woocommerce'), esc_attr($min_len)));
|
||||
} else {
|
||||
/* translators: %1$s is the minimum number of characters, %2$s is the maximum number of characters */
|
||||
throw new Exception(sprintf(esc_html__('The value must be between %1$s and %2$s characters.', 'dhl-for-woocommerce'), esc_attr($min_len), esc_attr($max_len)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
if (! is_numeric($value)) {
|
||||
throw new Exception(esc_html__('The value must be a number', 'dhl-for-woocommerce'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// abstract protected function parse_response( );
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
abstract class PR_DHL_API
|
||||
{
|
||||
protected $dhl_label = null;
|
||||
|
||||
protected $dhl_finder = null;
|
||||
|
||||
protected $country_code;
|
||||
|
||||
// abstract public function set_dhl_auth( $client_id, $client_secret );
|
||||
|
||||
public function is_dhl_paket()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function is_dhl_deutsche_post()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get_dhl_label($args)
|
||||
{
|
||||
return $this->dhl_label->get_dhl_label($args);
|
||||
}
|
||||
|
||||
public function delete_dhl_label($label_url)
|
||||
{
|
||||
return $this->dhl_label->delete_dhl_label($label_url);
|
||||
}
|
||||
|
||||
public function get_parcel_location($args)
|
||||
{
|
||||
if ($this->dhl_finder) {
|
||||
return $this->dhl_finder->get_parcel_location($args);
|
||||
} else {
|
||||
throw new Exception(esc_html__('Parcel Finder not available', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function get_dhl_products_international();
|
||||
|
||||
abstract public function get_dhl_products_domestic();
|
||||
|
||||
public function get_dhl_content_indicator()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function dhl_test_connection($client_id, $client_secret)
|
||||
{
|
||||
return $this->dhl_label->dhl_test_connection($client_id, $client_secret);
|
||||
}
|
||||
|
||||
public function dhl_validate_field($key, $value)
|
||||
{
|
||||
return $this->dhl_label->dhl_validate_field($key, $value);
|
||||
}
|
||||
|
||||
public function dhl_reset_connection() {}
|
||||
|
||||
public function get_dhl_preferred_day_time($postcode, $account_num, $cutoff_time, $working_days)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function get_dhl_duties()
|
||||
{
|
||||
$duties = [
|
||||
'DDU' => esc_html__('Delivery Duty Unpaid', 'dhl-for-woocommerce'),
|
||||
'DDP' => esc_html__('Delivery Duty Paid', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
return $duties;
|
||||
}
|
||||
|
||||
public function get_dhl_visual_age()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
// Singleton API connection class
|
||||
class PR_DHL_API_Auth_REST
|
||||
{
|
||||
/**
|
||||
* define Auth API endpoint
|
||||
*/
|
||||
const PR_DHL_REST_AUTH_END_POINT = '/account/v1/auth/accesstoken';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $access_token;
|
||||
|
||||
/**
|
||||
* @var time
|
||||
*/
|
||||
protected $token_expires = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $token_type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $token_scope;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_secret;
|
||||
|
||||
/**
|
||||
* @var PR_DHL_API_Auth_REST
|
||||
*/
|
||||
private static $_instance; // The single instance
|
||||
|
||||
/**
|
||||
* constructor.
|
||||
*/
|
||||
private function __construct() {}
|
||||
|
||||
// Magic method clone is empty to prevent duplication of connection
|
||||
private function __clone() {}
|
||||
|
||||
// Stopping unserialize of object
|
||||
public function __wakeup() {}
|
||||
|
||||
public static function get_instance()
|
||||
{
|
||||
|
||||
if ((! self::$_instance)) {
|
||||
self::$_instance = new self;
|
||||
}
|
||||
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_access_token($client_id, $client_secret)
|
||||
{
|
||||
|
||||
// Load transient token
|
||||
$this->get_saved_token($client_id, $client_secret);
|
||||
|
||||
if ($this->is_access_token_empty()) {
|
||||
try {
|
||||
|
||||
$this->request_access_token($client_id, $client_secret);
|
||||
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->access_token;
|
||||
}
|
||||
|
||||
public function delete_access_token()
|
||||
{
|
||||
PR_DHL()->log_msg('Delete Transient - Access Token');
|
||||
delete_transient('_dhl_auth_token_rest');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $access_token
|
||||
*/
|
||||
protected function set_access_token($access_token, $expires_in = 0, $token_type = '', $token_scope = '')
|
||||
{
|
||||
|
||||
if (! empty($expires_in)) {
|
||||
// $token_expires = time() + $expires_in;
|
||||
set_transient('_dhl_auth_token_rest', $access_token, $expires_in);
|
||||
PR_DHL()->log_msg('Set Transient - Access Token');
|
||||
}
|
||||
|
||||
$this->access_token = $access_token;
|
||||
$this->token_type = $token_type;
|
||||
$this->token_scope = $token_scope;
|
||||
}
|
||||
|
||||
private function get_saved_token($client_id, $client_secret)
|
||||
{
|
||||
|
||||
// if( $this->is_key_match($client_id, $client_secret) ) {
|
||||
// TRANSIENT MIGHT BE BEING USED IF KEY AND SECRET ARE NEW, EDGE CASE SO MIGHT BE OK!
|
||||
$transient_token = get_transient('_dhl_auth_token_rest');
|
||||
PR_DHL()->log_msg('Get Transient - Access Token');
|
||||
$this->set_access_token($transient_token);
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_access_token_empty()
|
||||
{
|
||||
return empty($this->access_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signed URL.
|
||||
* The signed URL is fetched by doing an OAuth request.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function request_access_token($client_id, $client_secret)
|
||||
{
|
||||
$this->client_id = $client_id;
|
||||
$this->client_secret = $client_secret;
|
||||
|
||||
if (empty($this->client_id) || empty($this->client_secret)) {
|
||||
throw new Exception(esc_html__('The "Cliend Id" or "Client Secret" is empty.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$wp_request_headers = [
|
||||
'Authorization' => 'Basic '.base64_encode($this->client_id.':'.$this->client_secret),
|
||||
];
|
||||
|
||||
$wp_request_url = PR_DHL()->get_api_url().self::PR_DHL_REST_AUTH_END_POINT;
|
||||
|
||||
PR_DHL()->log_msg('Authorization URL: '.$wp_request_url);
|
||||
|
||||
$wp_auth_response = wp_remote_get(
|
||||
$wp_request_url,
|
||||
['headers' => $wp_request_headers]
|
||||
);
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($wp_auth_response);
|
||||
$response_body = json_decode(wp_remote_retrieve_body($wp_auth_response));
|
||||
|
||||
PR_DHL()->log_msg('Authorization Response: '.$response_code);
|
||||
|
||||
switch ($response_code) {
|
||||
case '200':
|
||||
$this->set_access_token($response_body->access_token, $response_body->expires_in, $response_body->token_type, $response_body->scope);
|
||||
break;
|
||||
case '401':
|
||||
default:
|
||||
throw new Exception(esc_html__('Authentication failed: Please, check client ID and secret in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function is_key_match($client_id, $client_secret)
|
||||
{
|
||||
if (($this->client_id == $client_id) && ($this->client_secret == $client_secret)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,755 @@
|
|||
<?php
|
||||
|
||||
use PR\DHL\REST_API\Deutsche_Post\Auth;
|
||||
use PR\DHL\REST_API\Deutsche_Post\Client;
|
||||
use PR\DHL\REST_API\Deutsche_Post\Item_Info;
|
||||
use PR\DHL\REST_API\Drivers\JSON_API_Driver;
|
||||
use PR\DHL\REST_API\Drivers\Logging_Driver;
|
||||
use PR\DHL\REST_API\Drivers\WP_API_Driver;
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
|
||||
// Exit if accessed directly or class already exists
|
||||
if (! defined('ABSPATH') || class_exists('PR_DHL_API_Deutsche_Post', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class PR_DHL_API_Deutsche_Post extends PR_DHL_API
|
||||
{
|
||||
/**
|
||||
* The URL to the API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const API_URL_PRODUCTION = 'https://api.dhl.com/';
|
||||
|
||||
/**
|
||||
* The URL to the sandbox API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const API_URL_SANDBOX = 'https://api-sandbox.dhl.com/';
|
||||
|
||||
/**
|
||||
* The transient name where the API access token is stored.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const ACCESS_TOKEN_TRANSIENT = 'pr_dhl_deutsche_post_access_token';
|
||||
|
||||
/**
|
||||
* The API driver instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
public $api_driver;
|
||||
|
||||
/**
|
||||
* The API authorization instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var Auth
|
||||
*/
|
||||
public $api_auth;
|
||||
|
||||
/**
|
||||
* The API client instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var Client
|
||||
*/
|
||||
public $api_client;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $country_code The country code.
|
||||
*
|
||||
* @throws Exception If an error occurred while creating the API driver, auth or client.
|
||||
*/
|
||||
public function __construct($country_code)
|
||||
{
|
||||
$this->country_code = $country_code;
|
||||
|
||||
try {
|
||||
$this->api_driver = $this->create_api_driver();
|
||||
$this->api_auth = $this->create_api_auth();
|
||||
$this->api_client = $this->create_api_client();
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API client instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return Client
|
||||
*
|
||||
* @throws Exception If failed to create the API client.
|
||||
*/
|
||||
protected function create_api_client()
|
||||
{
|
||||
// Create the API client, using this instance's driver and auth objects
|
||||
return new Client(
|
||||
$this->get_ekp(),
|
||||
$this->get_contact_name(),
|
||||
$this->get_contact_phone(),
|
||||
$this->get_api_url(),
|
||||
$this->api_driver,
|
||||
$this->api_auth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API driver instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return API_Driver_Interface
|
||||
*
|
||||
* @throws Exception If failed to create the API driver.
|
||||
*/
|
||||
protected function create_api_driver()
|
||||
{
|
||||
// Use a standard WordPress-driven API driver to send requests using WordPress' functions
|
||||
$driver = new WP_API_Driver;
|
||||
|
||||
// This will log requests given to the original driver and log responses returned from it
|
||||
$driver = new Logging_Driver(PR_DHL(), $driver);
|
||||
|
||||
// This will prepare requests given to the previous driver for JSON content
|
||||
// and parse responses returned from it as JSON.
|
||||
$driver = new JSON_API_Driver($driver);
|
||||
|
||||
// , decorated using the JSON driver decorator class
|
||||
return $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API auth instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return API_Auth_Interface
|
||||
*
|
||||
* @throws Exception If failed to create the API auth.
|
||||
*/
|
||||
protected function create_api_auth()
|
||||
{
|
||||
// Get the saved DHL customer API credentials
|
||||
[$client_id, $client_secret] = $this->get_api_creds();
|
||||
|
||||
// Create the auth object using this instance's API driver and URL
|
||||
return new Auth(
|
||||
$this->api_driver,
|
||||
$this->get_api_url(),
|
||||
$client_id,
|
||||
$client_secret,
|
||||
static::ACCESS_TOKEN_TRANSIENT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function is_dhl_deutsche_post()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API URL.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If failed to determine if using the sandbox API or not.
|
||||
*/
|
||||
public function get_api_url()
|
||||
{
|
||||
$is_sandbox = $this->get_setting('dhl_sandbox');
|
||||
$is_sandbox = filter_var($is_sandbox, FILTER_VALIDATE_BOOLEAN);
|
||||
$api_url = ($is_sandbox) ? static::API_URL_SANDBOX : static::API_URL_PRODUCTION;
|
||||
|
||||
return $api_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API credentials.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array The client ID and client secret.
|
||||
*
|
||||
* @throws Exception If failed to retrieve the API credentials.
|
||||
*/
|
||||
public function get_api_creds()
|
||||
{
|
||||
return [
|
||||
$this->get_setting('dhl_api_key'),
|
||||
$this->get_setting('dhl_api_secret'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the DHL account number, or "EKP".
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If failed to retrieve the EKP from the settings.
|
||||
*/
|
||||
public function get_ekp()
|
||||
{
|
||||
return $this->get_setting('dhl_account_num');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the DHL contact name for orders.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If failed to retrieve the contact name from the settings.
|
||||
*/
|
||||
public function get_contact_name()
|
||||
{
|
||||
return $this->get_setting('dhl_contact_name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the DHL contact name for orders.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If failed to retrieve the contact name from the settings.
|
||||
*/
|
||||
public function get_contact_phone()
|
||||
{
|
||||
return $this->get_setting('dhl_contact_phone_number');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single setting.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $key The key of the setting to retrieve.
|
||||
* @param string $default The value to return if the setting is not saved.
|
||||
* @return mixed The setting value.
|
||||
*/
|
||||
public function get_setting($key, $default = '')
|
||||
{
|
||||
$settings = $this->get_settings();
|
||||
|
||||
return isset($settings[$key]) ? $settings[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the Deutsche Post settings.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array An associative array of the settings keys mapping to their values.
|
||||
*/
|
||||
public function get_settings()
|
||||
{
|
||||
return get_option('woocommerce_pr_dhl_dp_settings', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function dhl_test_connection($client_id, $client_secret)
|
||||
{
|
||||
try {
|
||||
// Test the given ID and secret
|
||||
$token = $this->api_auth->test_connection($client_id, $client_secret);
|
||||
// Save the token if successful
|
||||
$this->api_auth->save_token($token);
|
||||
|
||||
return $token;
|
||||
} catch (Exception $e) {
|
||||
$this->api_auth->save_token(null);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function dhl_reset_connection()
|
||||
{
|
||||
return $this->api_auth->revoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function get_dhl_products_international()
|
||||
{
|
||||
return [
|
||||
'GMP-STANDARD' => esc_html__('Packet Standard', 'dhl-for-woocommerce'),
|
||||
'GMP' => esc_html__('Packet Priority', 'dhl-for-woocommerce'),
|
||||
'GPP' => esc_html__('Packet Plus', 'dhl-for-woocommerce'),
|
||||
'GPT' => esc_html__('Packet Tracked', 'dhl-for-woocommerce'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function get_dhl_products_domestic()
|
||||
{
|
||||
return $this->get_dhl_products_international();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function get_dhl_label($args)
|
||||
{
|
||||
$order_id = isset($args['order_details']['order_id'])
|
||||
? $args['order_details']['order_id']
|
||||
: null;
|
||||
$order = wc_get_order($order_id);
|
||||
$item_barcode = $order->get_meta('pr_dhl_dp_item_barcode');
|
||||
|
||||
// If order has no saved barcode, create the DHL item and get the barcode
|
||||
if (empty($item_barcode)) {
|
||||
$uom = get_option('woocommerce_weight_unit');
|
||||
try {
|
||||
$item_info = new Item_Info($args, $uom);
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Create the item and get the barcode
|
||||
$item_response = $this->api_client->create_item($item_info);
|
||||
$item_barcode = $item_response->barcode;
|
||||
$item_id = $item_response->id;
|
||||
|
||||
// Save it in the order
|
||||
$order->update_meta_data('pr_dhl_dp_item_barcode', $item_barcode);
|
||||
$order->update_meta_data('pr_dhl_dp_item_id', $item_id);
|
||||
$order->save();
|
||||
|
||||
}
|
||||
|
||||
// Get the label for the created item
|
||||
$label_pdf_data = $this->api_client->get_item_label($item_barcode);
|
||||
// Save the label to a file
|
||||
$this->save_dhl_label_file('item', $item_barcode, $label_pdf_data);
|
||||
|
||||
return [
|
||||
'label_path' => $this->get_dhl_item_label_file_info($item_barcode)->path,
|
||||
'item_barcode' => $item_barcode,
|
||||
'tracking_number' => $item_barcode,
|
||||
'tracking_status' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function delete_dhl_label($label_info)
|
||||
{
|
||||
if (! isset($label_info['label_path'])) {
|
||||
throw new Exception(esc_html__('DHL Label has no path!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$label_path = $label_info['label_path'];
|
||||
|
||||
if (file_exists($label_path)) {
|
||||
$res = wp_delete_file($label_path);
|
||||
|
||||
if (! $res) {
|
||||
throw new Exception(esc_html__('DHL Label could not be deleted!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename for DHL item label files.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $barcode The DHL item barcode.
|
||||
* @param string $format The file format.
|
||||
* @return string
|
||||
*/
|
||||
public function get_dhl_item_label_file_name($barcode, $format = 'pdf')
|
||||
{
|
||||
// Translators: %1$s is the barcode number, and %2$s is the file format (e.g., pdf, jpg).
|
||||
return sprintf('dhl-label-%1$s.%2$s', $barcode, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename for DHL AWB label files.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $awb The AWB.
|
||||
* @param string $format The file format.
|
||||
* @return string
|
||||
*/
|
||||
public function get_dhl_awb_label_file_name($awb, $format = 'pdf')
|
||||
{
|
||||
// Translators: %1$s is the Air Waybill (AWB) number and %2$s is the file format (e.g., pdf, jpg).
|
||||
return sprintf('dhl-label-awb-%1$s.%2$s', $awb, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the filename for DHL order label files (a.k.a. merged AWB label files).
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $order_id The DHL order ID.
|
||||
* @param string $format The file format.
|
||||
* @return string
|
||||
*/
|
||||
public function get_dhl_order_label_file_name($order_id, $format = 'pdf')
|
||||
{
|
||||
// Translators: %1$s is the order ID and %2$s is the file format (e.g., pdf, jpg).
|
||||
return sprintf('dhl-waybill-order-%1$s.%2$s', $order_id, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the file info for a DHL item label file.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $barcode The DHL item barcode.
|
||||
* @param string $format The file format.
|
||||
* @return object An object containing the file "path" and "url" strings.
|
||||
*/
|
||||
public function get_dhl_item_label_file_info($barcode, $format = 'pdf')
|
||||
{
|
||||
$file_name = $this->get_dhl_item_label_file_name($barcode, $format);
|
||||
|
||||
return (object) [
|
||||
'path' => PR_DHL()->get_dhl_label_folder_dir().$file_name,
|
||||
'url' => PR_DHL()->get_dhl_label_folder_url().$file_name,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the file info for DHL AWB label files.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $awb The AWB.
|
||||
* @param string $format The file format.
|
||||
* @return object An object containing the file "path" and "url" strings.
|
||||
*/
|
||||
public function get_dhl_awb_label_file_info($awb, $format = 'pdf')
|
||||
{
|
||||
$file_name = $this->get_dhl_awb_label_file_name($awb, $format);
|
||||
|
||||
return (object) [
|
||||
'path' => PR_DHL()->get_dhl_label_folder_dir().$file_name,
|
||||
'url' => PR_DHL()->get_dhl_label_folder_url().$file_name,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the file info for DHL order label files (a.k.a. merged AWB label files).
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $order_id The DHL order ID.
|
||||
* @param string $format The file format.
|
||||
* @return object An object containing the file "path" and "url" strings.
|
||||
*/
|
||||
public function get_dhl_order_label_file_info($order_id, $format = 'pdf')
|
||||
{
|
||||
$file_name = $this->get_dhl_order_label_file_name($order_id, $format);
|
||||
|
||||
return (object) [
|
||||
'path' => PR_DHL()->get_dhl_label_folder_dir().$file_name,
|
||||
'url' => PR_DHL()->get_dhl_label_folder_url().$file_name,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the file info for any DHL label file, based on type.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $type The label type: "item", "awb" or "order".
|
||||
* @param string $key The key: barcode for type "item", AWB for type "awb" and order ID for type "order".
|
||||
* @return object An object containing the file "path" and "url" strings.
|
||||
*/
|
||||
public function get_dhl_label_file_info($type, $key)
|
||||
{
|
||||
// Return file info for "awb" type
|
||||
if ($type === 'awb') {
|
||||
return $this->get_dhl_awb_label_file_info($key);
|
||||
}
|
||||
|
||||
// Return file info for "order" type
|
||||
if ($type === 'order') {
|
||||
return $this->get_dhl_order_label_file_info($key);
|
||||
}
|
||||
|
||||
// Return info for "item" type
|
||||
return $this->get_dhl_item_label_file_info($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an item label file.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $type The label type: "item", "awb" or "order".
|
||||
* @param string $key The key: barcode for type "item", AWB for type "awb" and order ID for type "order".
|
||||
* @param string $data The label file data.
|
||||
* @return object The info for the saved label file, containing the "path" and "url".
|
||||
*
|
||||
* @throws Exception If failed to save the label file.
|
||||
*/
|
||||
public function save_dhl_label_file($type, $key, $data)
|
||||
{
|
||||
// Get the file info based on type
|
||||
$file_info = $this->get_dhl_label_file_info($type, $key);
|
||||
|
||||
// Validate the file path
|
||||
if (validate_file($file_info->path) > 0 && validate_file($file_info->path) !== 2) {
|
||||
throw new Exception(esc_html__('Invalid file path!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$file_ret = file_put_contents($file_info->path, $data);
|
||||
|
||||
if (empty($file_ret)) {
|
||||
throw new Exception(esc_html__('DHL label file cannot be saved!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
return $file_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an AWB label file.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $type The label type: "item", "awb" or "order".
|
||||
* @param string $key The key: barcode for type "item", AWB for type "awb" and order ID for type "order".
|
||||
*
|
||||
* @throws Exception If the file could not be deleted.
|
||||
*/
|
||||
public function delete_dhl_label_file($type, $key)
|
||||
{
|
||||
// Get the file info based on type
|
||||
$file_info = $this->get_dhl_label_file_info($type, $key);
|
||||
|
||||
// Do nothing if file does not exist
|
||||
if (! file_exists($file_info->path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to delete the file
|
||||
$res = wp_delete_file($file_info->path);
|
||||
|
||||
// Throw error if the file could not be deleted
|
||||
if (! $res) {
|
||||
throw new Exception(esc_html__('DHL AWB Label could not be deleted!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an AWB label file already exist, and if not fetches it from the API and saves it.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $awb The AWB.
|
||||
* @return object An object containing the "path" and "url" to the label file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create_dhl_awb_label_file($awb)
|
||||
{
|
||||
$file_info = $this->get_dhl_awb_label_file_info($awb);
|
||||
|
||||
// Skip creating the file if it already exists
|
||||
if (file_exists($file_info->path)) {
|
||||
return $file_info;
|
||||
}
|
||||
|
||||
// Get the label data from the API client
|
||||
$label_data = $this->api_client->get_awb_label($awb);
|
||||
// Save the label file
|
||||
$this->save_dhl_label_file('awb', $awb, $label_data);
|
||||
|
||||
return $file_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an order label file already exist, and if not fetches it from the API and saves it.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $order_id The DHL order ID.
|
||||
* @return object An object containing the "path" and "url" to the label file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create_dhl_order_label_file($order_id)
|
||||
{
|
||||
$file_info = $this->get_dhl_order_label_file_info($order_id);
|
||||
|
||||
// Skip creating the file if it already exists
|
||||
if (file_exists($file_info->path)) {
|
||||
return $file_info;
|
||||
}
|
||||
|
||||
// Get the order with the given ID
|
||||
$order = $this->api_client->get_order($order_id);
|
||||
if ($order === null) {
|
||||
// Translators: %s is replaced with the order ID.
|
||||
throw new Exception(esc_html(sprintf('DHL order %s does not exist.', $order_id)));
|
||||
}
|
||||
|
||||
// For multiple shipments, maybe create each label file and then merge them
|
||||
$loader = PR_DHL_Libraryloader::instance();
|
||||
$pdfMerger = $loader->get_pdf_merger();
|
||||
|
||||
if ($pdfMerger === null) {
|
||||
|
||||
throw new Exception(esc_html__('Library conflict, could not merge PDF files. Please download PDF files individually.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
foreach ($order['shipments'] as $shipment) {
|
||||
// Create the single AWB label file
|
||||
$awb_label_info = $this->create_dhl_awb_label_file($shipment->awb);
|
||||
|
||||
// Ensure file exists
|
||||
if (! file_exists($awb_label_info->path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure it is a PDF file
|
||||
$ext = pathinfo($awb_label_info->path, PATHINFO_EXTENSION);
|
||||
if (stripos($ext, 'pdf') === false) {
|
||||
throw new Exception(esc_html__('Not all the file formats are the same.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Add to merge queue
|
||||
$pdfMerger->addPDF($awb_label_info->path, 'all');
|
||||
}
|
||||
|
||||
// Merge all files in the queue
|
||||
$pdfMerger->merge('file', $file_info->path);
|
||||
|
||||
return $file_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function dhl_validate_field($key, $value) {}
|
||||
|
||||
/**
|
||||
* Finalizes and creates the current Deutsche Post order.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string The ID of the created DHL order.
|
||||
*
|
||||
* @throws Exception If an error occurred while and the API failed to create the order.
|
||||
*/
|
||||
public function create_order($copy_count = 1)
|
||||
{
|
||||
// Create the DHL order
|
||||
$response = $this->api_client->create_order($copy_count);
|
||||
|
||||
$this->get_settings();
|
||||
|
||||
// Get the current DHL order - the one that was just submitted
|
||||
$order = $this->api_client->get_order($response->orderId);
|
||||
$order_items = $order['items'];
|
||||
|
||||
// Get the tracking note type setting
|
||||
// $tracking_note_type = $this->get_setting('dhl_tracking_note', 'customer');
|
||||
// $tracking_note_type = ($tracking_note_type == 'yes') ? '' : 'customer';
|
||||
|
||||
// Go through the shipments retrieved from the API and save the AWB of the shipment to
|
||||
// each DHL item's associated WooCommerce order in post meta. This will make sure that each
|
||||
// WooCommerce order has a reference to the its DHL shipment AWB.
|
||||
// At the same time, we will be collecting the AWBs to merge the label PDFs later on, as well
|
||||
// as adding order notes for the AWB to each WC order.
|
||||
$awbs = [];
|
||||
foreach ($response->shipments as $shipment) {
|
||||
foreach ($shipment->items as $item) {
|
||||
if (! isset($order_items[$item->barcode])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the WC order for this DHL item
|
||||
$item_wc_order_id = $order_items[$item->barcode];
|
||||
$item_wc_order = wc_get_order($item_wc_order_id);
|
||||
|
||||
// Save the AWB to the WC order
|
||||
$item_wc_order->update_meta_data('pr_dhl_dp_awb', $shipment->awb);
|
||||
|
||||
// An an order note for the AWB
|
||||
$item_awb_note = esc_html__('Shipment AWB: ', 'dhl-for-woocommerce').$shipment->awb;
|
||||
// 'type' should alwasys be private for AWB
|
||||
$item_wc_order->add_order_note($item_awb_note, '', true);
|
||||
|
||||
// Save the AWB in the list.
|
||||
$awbs[] = $shipment->awb;
|
||||
|
||||
// Save the DHL order ID in the WC order meta
|
||||
$item_wc_order->update_meta_data('pr_dhl_dp_order', $response->orderId);
|
||||
$item_wc_order->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the merged AWB label file
|
||||
$this->create_dhl_order_label_file($response->orderId);
|
||||
|
||||
return $response->orderId;
|
||||
}
|
||||
|
||||
public function get_dhl_nature_type()
|
||||
{
|
||||
return [
|
||||
'SALE_GOODS' => esc_html__('Sale Goods', 'dhl-for-woocommerce'),
|
||||
'RETURN_GOODS' => esc_html__('Return Goods', 'dhl-for-woocommerce'),
|
||||
'GIFT' => esc_html__('Gift', 'dhl-for-woocommerce'),
|
||||
'COMMERCIAL_SAMPLE' => esc_html__('Commercial Sample', 'dhl-for-woocommerce'),
|
||||
'DOCUMENTS' => esc_html__('Documents', 'dhl-for-woocommerce'),
|
||||
'MIXED_CONTENTS' => esc_html__('Mixed Contents', 'dhl-for-woocommerce'),
|
||||
'OTHERS' => esc_html__('Others', 'dhl-for-woocommerce'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
class PR_DHL_API_Factory
|
||||
{
|
||||
public static function init()
|
||||
{
|
||||
// Load abstract classes
|
||||
include_once 'abstract-pr-dhl-api-rest.php';
|
||||
include_once 'abstract-pr-dhl-api.php';
|
||||
|
||||
// Load interfaces
|
||||
include_once 'interface-pr-dhl-api-label.php';
|
||||
}
|
||||
|
||||
public static function make_dhl($country_code)
|
||||
{
|
||||
static $cache = [];
|
||||
|
||||
// If object exists in cache, simply return it.
|
||||
if (array_key_exists($country_code, $cache)) {
|
||||
return $cache[$country_code];
|
||||
}
|
||||
|
||||
self::init();
|
||||
|
||||
switch ($country_code) {
|
||||
case 'DE':
|
||||
$dhl_obj = new PR_DHL_API_Paket($country_code);
|
||||
break;
|
||||
case 'AT':
|
||||
case 'AL':
|
||||
case 'AD':
|
||||
case 'AM':
|
||||
case 'AZ':
|
||||
case 'BY':
|
||||
case 'BE':
|
||||
case 'BA':
|
||||
case 'BG':
|
||||
case 'HR':
|
||||
case 'CY':
|
||||
case 'CZ':
|
||||
case 'DK':
|
||||
case 'EE':
|
||||
case 'FI':
|
||||
case 'FR':
|
||||
case 'GE':
|
||||
case 'GR':
|
||||
case 'HU':
|
||||
case 'IS':
|
||||
case 'IE':
|
||||
case 'IT':
|
||||
case 'KM':
|
||||
case 'LV':
|
||||
case 'LI':
|
||||
case 'LT':
|
||||
case 'LU':
|
||||
case 'MT':
|
||||
case 'MD':
|
||||
case 'MC':
|
||||
case 'ME':
|
||||
case 'NL':
|
||||
case 'MK':
|
||||
case 'NO':
|
||||
case 'PL':
|
||||
case 'PT':
|
||||
case 'RO':
|
||||
case 'RU':
|
||||
case 'SM':
|
||||
case 'RS':
|
||||
case 'SK':
|
||||
case 'SI':
|
||||
case 'ES':
|
||||
case 'SE':
|
||||
case 'CH':
|
||||
case 'TR':
|
||||
case 'UA':
|
||||
case 'GB':
|
||||
case 'VA':
|
||||
$dhl_obj = new PR_DHL_API_Deutsche_Post($country_code);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(esc_html__('The DHL plugin is not supported in your store\'s "Base Location"', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Cache the object to optimize later invocations of the factory
|
||||
$cache[$country_code] = $dhl_obj;
|
||||
|
||||
return $dhl_obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,516 @@
|
|||
<?php
|
||||
|
||||
use PR\DHL\REST_API\Drivers\JSON_API_Driver;
|
||||
use PR\DHL\REST_API\Drivers\Logging_Driver;
|
||||
use PR\DHL\REST_API\Drivers\WP_API_Driver;
|
||||
use PR\DHL\REST_API\Internetmarke\Auth;
|
||||
use PR\DHL\REST_API\Internetmarke\Client;
|
||||
|
||||
if (! defined('ABSPATH') || class_exists('PR_DHL_API_Internetmarke', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class PR_DHL_API_Internetmarke
|
||||
{
|
||||
const API_URL = 'https://api-eu.dhl.com/post/de/shipping/im/v1';
|
||||
|
||||
const ACCESS_TOKEN_TRANSIENT = 'pr_dhl_internetmarke_access_token';
|
||||
|
||||
/**
|
||||
* Polymorphic discriminators for the shopping-cart PDF checkout request.
|
||||
*
|
||||
* The request body and its positions are polymorphic: the root needs `type`
|
||||
* and each position needs `positionType`. Values verified against the
|
||||
* INTERNETMARKE OpenAPI spec and confirmed live (a correct body reaches the
|
||||
* payment step / walletBalanceNotEnough instead of InvalidTypeIdException).
|
||||
*/
|
||||
const REQUEST_TYPE = 'AppShoppingCartPDFRequest';
|
||||
|
||||
const POSITION_TYPE = 'AppShoppingCartPDFPosition';
|
||||
|
||||
/**
|
||||
* Default print format id (plain A4). Available formats: GET /app/catalog?types=PAGE_FORMATS.
|
||||
* Should be made configurable in a follow-up.
|
||||
*/
|
||||
const PAGE_FORMAT_ID = 1;
|
||||
|
||||
protected $driver;
|
||||
|
||||
protected $auth;
|
||||
|
||||
protected $client;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$raw_driver = new WP_API_Driver;
|
||||
$logging_driver = new Logging_Driver(PR_DHL(), $raw_driver);
|
||||
$this->driver = new JSON_API_Driver($logging_driver);
|
||||
|
||||
// Auth uses the non-logging driver so the token request body (which carries
|
||||
// the Portokasse password and app secret) is never written to the log.
|
||||
$this->auth = new Auth(
|
||||
$raw_driver,
|
||||
static::API_URL,
|
||||
$this->get_app_client_id(),
|
||||
$this->get_app_client_secret(),
|
||||
$this->get_username(),
|
||||
$this->get_password(),
|
||||
static::ACCESS_TOKEN_TRANSIENT
|
||||
);
|
||||
$this->client = new Client(static::API_URL, $this->driver, $this->auth);
|
||||
}
|
||||
|
||||
public function test_connection()
|
||||
{
|
||||
$this->validate_configuration();
|
||||
$this->log('Testing INTERNETMARKE connection.');
|
||||
|
||||
$token = $this->auth->test_connection();
|
||||
|
||||
$this->auth->save_token($token);
|
||||
$this->log('INTERNETMARKE connection test succeeded.');
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function validate_configuration()
|
||||
{
|
||||
if ($this->get_username() === '' || $this->get_password() === '') {
|
||||
throw new Exception(esc_html__('Please save the INTERNETMARKE Portokasse username and password before running this action.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
protected function get_app_client_id()
|
||||
{
|
||||
return defined('PR_DHL_GLOBAL_API') ? PR_DHL_GLOBAL_API : '';
|
||||
}
|
||||
|
||||
protected function get_app_client_secret()
|
||||
{
|
||||
return defined('PR_DHL_GLOBAL_SECRET') ? PR_DHL_GLOBAL_SECRET : '';
|
||||
}
|
||||
|
||||
protected function get_settings()
|
||||
{
|
||||
$paket = get_option('woocommerce_pr_dhl_paket_settings', []);
|
||||
|
||||
return [
|
||||
'internetmarke_api_user' => isset($paket['internetmarke_api_user']) ? $paket['internetmarke_api_user'] : '',
|
||||
'internetmarke_api_password' => isset($paket['internetmarke_api_password']) ? $paket['internetmarke_api_password'] : '',
|
||||
'internetmarke_portokasse_id' => isset($paket['internetmarke_portokasse_id']) ? $paket['internetmarke_portokasse_id'] : '',
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_username()
|
||||
{
|
||||
$settings = $this->get_settings();
|
||||
|
||||
return trim((string) $settings['internetmarke_api_user']);
|
||||
}
|
||||
|
||||
protected function get_password()
|
||||
{
|
||||
$settings = $this->get_settings();
|
||||
|
||||
return (string) $settings['internetmarke_api_password'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Purchase an INTERNETMARKE stamp for a WooCommerce order.
|
||||
*
|
||||
* This performs ONLY the paid checkout (directCheckout=true debits the Portokasse
|
||||
* wallet). Downloading the document is a separate, retry-safe step so a download
|
||||
* failure can never force a second charge — the caller must persist the returned
|
||||
* voucher before calling download_voucher().
|
||||
*
|
||||
* @param int $order_id WooCommerce order ID (also sent as the shopOrderId).
|
||||
* @param int $product_id Resolved INTERNETMARKE product ID.
|
||||
* @return array { shop_order_id: string, remote_link: string, tracking_number: string }
|
||||
*
|
||||
* @throws Exception Only when the checkout itself fails (no charge occurred).
|
||||
*/
|
||||
public function purchase_label($order_id, $product_id)
|
||||
{
|
||||
$this->validate_configuration();
|
||||
$this->log('Purchasing label for order '.$order_id.', product ID '.$product_id.'.');
|
||||
|
||||
$order = wc_get_order($order_id);
|
||||
if (! is_a($order, 'WC_Order')) {
|
||||
throw new Exception(esc_html__('Could not load the WooCommerce order for INTERNETMARKE label generation.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$payload = $this->build_label_payload($order, $product_id);
|
||||
$result = $this->client->create_label($payload);
|
||||
|
||||
$this->log('Stamp purchased for order '.$order_id.'.');
|
||||
|
||||
return $this->extract_voucher($result, (string) $order->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-fetch an already purchased stamp via GET /app/shoppingcart/{shopOrderId}.
|
||||
*
|
||||
* Documented read-only recovery path: it returns the same checkout response
|
||||
* (including a fresh document link) WITHOUT charging again. Used to recover the
|
||||
* PDF when a download failed after a successful purchase, or when the original
|
||||
* link expired.
|
||||
*
|
||||
* @param string $shop_order_id The shopOrderId used at checkout (the WC order ID).
|
||||
* @return array { shop_order_id: string, remote_link: string, tracking_number: string }
|
||||
*
|
||||
* @throws Exception When the cart cannot be retrieved.
|
||||
*/
|
||||
public function retrieve_label($shop_order_id)
|
||||
{
|
||||
$this->validate_configuration();
|
||||
$this->log('Retrieving purchased stamp for shop order '.$shop_order_id.'.');
|
||||
|
||||
$result = $this->client->get_shopping_cart($shop_order_id);
|
||||
|
||||
return $this->extract_voucher($result, (string) $shop_order_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the voucher fields (document link, tracking number) from a checkout
|
||||
* or cart-retrieval response.
|
||||
*
|
||||
* @param object|null $result Decoded CheckoutShoppingCartPDFResponse.
|
||||
* @param string $shop_order_id The shopOrderId this voucher belongs to.
|
||||
* @return array { shop_order_id: string, remote_link: string, tracking_number: string }
|
||||
*/
|
||||
protected function extract_voucher($result, $shop_order_id)
|
||||
{
|
||||
$voucher = [
|
||||
'shop_order_id' => (string) $shop_order_id,
|
||||
'remote_link' => '',
|
||||
'tracking_number' => '',
|
||||
];
|
||||
|
||||
if (is_object($result)) {
|
||||
if (! empty($result->link)) {
|
||||
$voucher['remote_link'] = (string) $result->link;
|
||||
}
|
||||
|
||||
// CheckoutShoppingCartPDFResponse returns vouchers under shoppingCart->voucherList;
|
||||
// trackId is present per voucher for registered products (EINSCHREIBEN).
|
||||
$vouchers = isset($result->shoppingCart->voucherList) ? $result->shoppingCart->voucherList : [];
|
||||
if (! empty($vouchers) && is_array($vouchers)) {
|
||||
foreach ($vouchers as $entry) {
|
||||
if (! empty($entry->trackId)) {
|
||||
$voucher['tracking_number'] = (string) $entry->trackId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $voucher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the AppShoppingCartPDFRequest payload for POST /app/shoppingcart/pdf?directCheckout=true.
|
||||
*
|
||||
* Structure verified live against the API: a correct body reaches the payment
|
||||
* step (walletBalanceNotEnough on an unfunded dev Portokasse). `total` must equal
|
||||
* the voucher price in euro-cents, so it is looked up per product code.
|
||||
*
|
||||
* @param int $product_id Resolved INTERNETMARKE product code.
|
||||
* @return array
|
||||
*/
|
||||
protected function build_label_payload(WC_Order $order, $product_id)
|
||||
{
|
||||
return [
|
||||
'type' => self::REQUEST_TYPE,
|
||||
'shopOrderId' => (string) $order->get_id(),
|
||||
'pageFormatId' => self::PAGE_FORMAT_ID,
|
||||
'total' => $this->get_product_price_cents($product_id),
|
||||
'createManifest' => false,
|
||||
'createShippingList' => '0',
|
||||
'dpi' => 'DPI300',
|
||||
'positions' => [
|
||||
[
|
||||
'positionType' => self::POSITION_TYPE,
|
||||
'productCode' => (int) $product_id,
|
||||
'voucherLayout' => 'ADDRESS_ZONE',
|
||||
'position' => [
|
||||
'labelX' => 1,
|
||||
'labelY' => 1,
|
||||
'page' => 1,
|
||||
],
|
||||
'address' => [
|
||||
'sender' => $this->get_sender_address(),
|
||||
'receiver' => $this->get_recipient_address($order),
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Voucher price in euro-cents for a resolved INTERNETMARKE product code.
|
||||
*
|
||||
* Prices taken from the official Deutsche Post product price list (ppl_v590_PARTNER),
|
||||
* covering the approved product scope. `total` is validated by the API on checkout,
|
||||
* so these must stay in sync with the current PPL — ideally synced from the catalog
|
||||
* API in a follow-up rather than hard-coded.
|
||||
*
|
||||
* @param int $product_id Resolved INTERNETMARKE product code.
|
||||
* @return int Price in euro-cents.
|
||||
*
|
||||
* @throws Exception If no price is configured for the product.
|
||||
*/
|
||||
protected function get_product_price_cents($product_id)
|
||||
{
|
||||
static $prices = [
|
||||
11 => 110, 21 => 180, 31 => 290, 41 => 510, 290 => 270, 331 => 355,
|
||||
1012 => 345, 1017 => 375, 1018 => 595,
|
||||
1022 => 415, 1027 => 445, 1028 => 665,
|
||||
1032 => 525, 1037 => 555, 1038 => 775,
|
||||
1042 => 745, 1047 => 775, 1048 => 995,
|
||||
10011 => 180, 10051 => 330, 10071 => 650, 10091 => 1700,
|
||||
11016 => 550, 11056 => 700, 11076 => 1020, 11096 => 2070,
|
||||
];
|
||||
|
||||
$id = (int) $product_id;
|
||||
|
||||
if (! isset($prices[$id])) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
/* translators: %d: INTERNETMARKE product code. */
|
||||
esc_html__('No price is configured for INTERNETMARKE product %d.', 'dhl-for-woocommerce'),
|
||||
$id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $prices[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the voucher PDF from Deutsche Post's URL and save it locally.
|
||||
*
|
||||
* Deutsche Post returns a link that is not guaranteed to stay valid indefinitely,
|
||||
* so the merchant's "Download label" button must not depend on it. We download the
|
||||
* PDF immediately and store it in the same woocommerce_dhl_label/ directory used by
|
||||
* DHL Paket labels, so labels are accessible permanently. If a fresh link is needed
|
||||
* (e.g. the stored one expired), retrieve_label() re-fetches one without charging.
|
||||
*
|
||||
* Retry-safe: this never contacts the paid checkout endpoint, so calling it again
|
||||
* after a failure does not charge the Portokasse.
|
||||
*
|
||||
* @param string $remote_url URL returned in the API response link field.
|
||||
* @param int $order_id WooCommerce order ID (used for the filename).
|
||||
* @return string Local web-accessible URL to the saved PDF.
|
||||
*
|
||||
* @throws Exception On HTTP error or file-system failure.
|
||||
*/
|
||||
public function download_voucher($remote_url, $order_id)
|
||||
{
|
||||
PR_DHL()->dhl_label_folder_check();
|
||||
|
||||
$dir = PR_DHL()->get_dhl_label_folder_dir();
|
||||
$web = PR_DHL()->get_dhl_label_folder_url();
|
||||
|
||||
if (empty($dir)) {
|
||||
throw new Exception(esc_html__('Could not create the DHL label folder.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$filename = 'dhl-im-label-'.(int) $order_id.'.pdf';
|
||||
$file_path = $dir.$filename;
|
||||
$file_url = $web.$filename;
|
||||
|
||||
// validate_file() returns 2 for absolute paths on Windows — that is safe.
|
||||
if (validate_file($file_path) > 0 && validate_file($file_path) !== 2) {
|
||||
throw new Exception(esc_html__('Invalid INTERNETMARKE label file path.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$response = wp_remote_get($remote_url, ['timeout' => 30]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
$this->log('Label PDF download error for order '.$order_id.': '.$response->get_error_message());
|
||||
throw new Exception($response->get_error_message());
|
||||
}
|
||||
|
||||
$http_code = (int) wp_remote_retrieve_response_code($response);
|
||||
if ($http_code !== 200) {
|
||||
$this->log('Label PDF download for order '.$order_id.' returned HTTP '.$http_code.'.');
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
/* translators: %d: HTTP status code */
|
||||
esc_html__('Could not download INTERNETMARKE label (HTTP %d).', 'dhl-for-woocommerce'),
|
||||
$http_code
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$pdf = wp_remote_retrieve_body($response);
|
||||
|
||||
if (empty($pdf)) {
|
||||
throw new Exception(esc_html__('Empty response when downloading INTERNETMARKE label.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$written = file_put_contents($file_path, $pdf); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||
|
||||
if ($written === false || $written === 0) {
|
||||
throw new Exception(esc_html__('Could not save INTERNETMARKE label file.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$this->log('Label PDF saved locally for order '.$order_id.': '.$filename.' ('.strlen($pdf).' bytes).');
|
||||
|
||||
return $file_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the sender address from Paket shipper settings with WC store address as fallback.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_sender_address()
|
||||
{
|
||||
$paket = get_option('woocommerce_pr_dhl_paket_settings', []);
|
||||
|
||||
$name = ! empty($paket['dhl_shipper_name']) ? $paket['dhl_shipper_name'] : get_bloginfo('name');
|
||||
$street = ! empty($paket['dhl_shipper_address']) ? $paket['dhl_shipper_address'] : '';
|
||||
$house = ! empty($paket['dhl_shipper_address_no']) ? $paket['dhl_shipper_address_no'] : '';
|
||||
$city = ! empty($paket['dhl_shipper_address_city']) ? $paket['dhl_shipper_address_city'] : get_option('woocommerce_store_city', '');
|
||||
$zip = ! empty($paket['dhl_shipper_address_zip']) ? $paket['dhl_shipper_address_zip'] : get_option('woocommerce_store_postcode', '');
|
||||
|
||||
$line1 = trim($street.' '.$house);
|
||||
if ($line1 === '') {
|
||||
$line1 = get_option('woocommerce_store_address', '');
|
||||
}
|
||||
|
||||
$address = [
|
||||
'name' => (string) $name,
|
||||
'addressLine1' => (string) $line1,
|
||||
'postalCode' => (string) $zip,
|
||||
'city' => (string) $city,
|
||||
'country' => 'DEU', // Sender is always in Germany for INTERNETMARKE.
|
||||
];
|
||||
|
||||
return array_filter($address, 'strlen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the recipient address from the WC order shipping address.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_recipient_address(WC_Order $order)
|
||||
{
|
||||
$shipping = $order->get_address('shipping');
|
||||
$billing = $order->get_address();
|
||||
|
||||
$first_name = ! empty($shipping['first_name']) ? $shipping['first_name'] : (isset($billing['first_name']) ? $billing['first_name'] : '');
|
||||
$last_name = ! empty($shipping['last_name']) ? $shipping['last_name'] : (isset($billing['last_name']) ? $billing['last_name'] : '');
|
||||
$company = ! empty($shipping['company']) ? $shipping['company'] : '';
|
||||
$name = trim($first_name.' '.$last_name);
|
||||
|
||||
// A person name goes in `name` and the company in `additionalName`; when only a
|
||||
// company is given it becomes the name.
|
||||
if ($name === '') {
|
||||
$name = $company;
|
||||
$company = '';
|
||||
}
|
||||
|
||||
$address_1 = ! empty($shipping['address_1']) ? $shipping['address_1'] : (isset($billing['address_1']) ? $billing['address_1'] : '');
|
||||
$address_2 = ! empty($shipping['address_2']) ? $shipping['address_2'] : '';
|
||||
$postcode = ! empty($shipping['postcode']) ? $shipping['postcode'] : (isset($billing['postcode']) ? $billing['postcode'] : '');
|
||||
$city = ! empty($shipping['city']) ? $shipping['city'] : (isset($billing['city']) ? $billing['city'] : '');
|
||||
$country = ! empty($shipping['country']) ? $shipping['country'] : (isset($billing['country']) ? $billing['country'] : 'DE');
|
||||
|
||||
$country_iso3 = $this->country_iso2_to_iso3($country);
|
||||
if (strlen($country_iso3) !== 3) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
/* translators: %s: two-letter destination country code. */
|
||||
esc_html__('INTERNETMARKE does not support the destination country "%s".', 'dhl-for-woocommerce'),
|
||||
sanitize_text_field((string) $country)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$address = [
|
||||
'name' => $name,
|
||||
'additionalName' => $company,
|
||||
'addressLine1' => $address_1,
|
||||
'addressLine2' => $address_2,
|
||||
'postalCode' => $postcode,
|
||||
'city' => $city,
|
||||
'country' => $country_iso3,
|
||||
];
|
||||
|
||||
return array_filter($address, 'strlen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert ISO 3166-1 alpha-2 to alpha-3 (required by INTERNETMARKE API).
|
||||
*
|
||||
* Complete mapping covering all UN member states and commonly-used territories.
|
||||
*
|
||||
* @param string $iso2 Two-letter country code.
|
||||
* @return string Three-letter country code, or original value if unknown.
|
||||
*/
|
||||
protected function country_iso2_to_iso3($iso2)
|
||||
{
|
||||
static $map = [
|
||||
// Europe
|
||||
'AD' => 'AND', 'AL' => 'ALB', 'AM' => 'ARM', 'AT' => 'AUT', 'AZ' => 'AZE',
|
||||
'BA' => 'BIH', 'BE' => 'BEL', 'BG' => 'BGR', 'BY' => 'BLR', 'CH' => 'CHE',
|
||||
'CY' => 'CYP', 'CZ' => 'CZE', 'DE' => 'DEU', 'DK' => 'DNK', 'EE' => 'EST',
|
||||
'ES' => 'ESP', 'FI' => 'FIN', 'FR' => 'FRA', 'GB' => 'GBR', 'GE' => 'GEO',
|
||||
'GR' => 'GRC', 'HR' => 'HRV', 'HU' => 'HUN', 'IE' => 'IRL', 'IS' => 'ISL',
|
||||
'IT' => 'ITA', 'LI' => 'LIE', 'LT' => 'LTU', 'LU' => 'LUX', 'LV' => 'LVA',
|
||||
'MC' => 'MCO', 'MD' => 'MDA', 'ME' => 'MNE', 'MK' => 'MKD', 'MT' => 'MLT',
|
||||
'NL' => 'NLD', 'NO' => 'NOR', 'PL' => 'POL', 'PT' => 'PRT', 'RO' => 'ROU',
|
||||
'RS' => 'SRB', 'RU' => 'RUS', 'SE' => 'SWE', 'SI' => 'SVN', 'SK' => 'SVK',
|
||||
'SM' => 'SMR', 'TR' => 'TUR', 'UA' => 'UKR', 'VA' => 'VAT', 'XK' => 'XKX',
|
||||
// Asia
|
||||
'AE' => 'ARE', 'AF' => 'AFG', 'BD' => 'BGD', 'BH' => 'BHR', 'BN' => 'BRN',
|
||||
'BT' => 'BTN', 'CN' => 'CHN', 'HK' => 'HKG', 'ID' => 'IDN', 'IL' => 'ISR',
|
||||
'IN' => 'IND', 'IQ' => 'IRQ', 'IR' => 'IRN', 'JO' => 'JOR', 'JP' => 'JPN',
|
||||
'KG' => 'KGZ', 'KH' => 'KHM', 'KP' => 'PRK', 'KR' => 'KOR', 'KW' => 'KWT',
|
||||
'KZ' => 'KAZ', 'LA' => 'LAO', 'LB' => 'LBN', 'LK' => 'LKA', 'MM' => 'MMR',
|
||||
'MN' => 'MNG', 'MO' => 'MAC', 'MV' => 'MDV', 'MY' => 'MYS', 'NP' => 'NPL',
|
||||
'OM' => 'OMN', 'PH' => 'PHL', 'PK' => 'PAK', 'PS' => 'PSE', 'QA' => 'QAT',
|
||||
'SA' => 'SAU', 'SG' => 'SGP', 'SY' => 'SYR', 'TH' => 'THA', 'TJ' => 'TJK',
|
||||
'TL' => 'TLS', 'TM' => 'TKM', 'TW' => 'TWN', 'UZ' => 'UZB', 'VN' => 'VNM',
|
||||
'YE' => 'YEM',
|
||||
// North & Central America
|
||||
'AG' => 'ATG', 'BB' => 'BRB', 'BS' => 'BHS', 'BZ' => 'BLZ', 'CA' => 'CAN',
|
||||
'CR' => 'CRI', 'CU' => 'CUB', 'DM' => 'DMA', 'DO' => 'DOM', 'GD' => 'GRD',
|
||||
'GT' => 'GTM', 'HN' => 'HND', 'HT' => 'HTI', 'JM' => 'JAM', 'KN' => 'KNA',
|
||||
'LC' => 'LCA', 'MX' => 'MEX', 'NI' => 'NIC', 'PA' => 'PAN', 'PR' => 'PRI',
|
||||
'SV' => 'SLV', 'TT' => 'TTO', 'US' => 'USA', 'VC' => 'VCT',
|
||||
// South America
|
||||
'AR' => 'ARG', 'BO' => 'BOL', 'BR' => 'BRA', 'CL' => 'CHL', 'CO' => 'COL',
|
||||
'EC' => 'ECU', 'GY' => 'GUY', 'PE' => 'PER', 'PY' => 'PRY', 'SR' => 'SUR',
|
||||
'UY' => 'URY', 'VE' => 'VEN',
|
||||
// Africa
|
||||
'AO' => 'AGO', 'BF' => 'BFA', 'BI' => 'BDI', 'BJ' => 'BEN', 'BW' => 'BWA',
|
||||
'CD' => 'COD', 'CF' => 'CAF', 'CG' => 'COG', 'CI' => 'CIV', 'CM' => 'CMR',
|
||||
'CV' => 'CPV', 'DJ' => 'DJI', 'DZ' => 'DZA', 'EG' => 'EGY', 'ER' => 'ERI',
|
||||
'ET' => 'ETH', 'GA' => 'GAB', 'GH' => 'GHA', 'GM' => 'GMB', 'GN' => 'GIN',
|
||||
'GQ' => 'GNQ', 'GW' => 'GNB', 'KE' => 'KEN', 'KM' => 'COM', 'LR' => 'LBR',
|
||||
'LS' => 'LSO', 'LY' => 'LBY', 'MA' => 'MAR', 'MG' => 'MDG', 'ML' => 'MLI',
|
||||
'MR' => 'MRT', 'MU' => 'MUS', 'MW' => 'MWI', 'MZ' => 'MOZ', 'NA' => 'NAM',
|
||||
'NE' => 'NER', 'NG' => 'NGA', 'RW' => 'RWA', 'SC' => 'SYC', 'SD' => 'SDN',
|
||||
'SL' => 'SLE', 'SN' => 'SEN', 'SO' => 'SOM', 'SS' => 'SSD', 'ST' => 'STP',
|
||||
'SZ' => 'SWZ', 'TD' => 'TCD', 'TG' => 'TGO', 'TN' => 'TUN', 'TZ' => 'TZA',
|
||||
'UG' => 'UGA', 'ZA' => 'ZAF', 'ZM' => 'ZMB', 'ZW' => 'ZWE',
|
||||
// Oceania
|
||||
'AU' => 'AUS', 'FJ' => 'FJI', 'FM' => 'FSM', 'KI' => 'KIR', 'MH' => 'MHL',
|
||||
'NR' => 'NRU', 'NZ' => 'NZL', 'PG' => 'PNG', 'PW' => 'PLW', 'SB' => 'SLB',
|
||||
'TO' => 'TON', 'TV' => 'TUV', 'VU' => 'VUT', 'WS' => 'WSM',
|
||||
];
|
||||
|
||||
$iso2 = strtoupper((string) $iso2);
|
||||
|
||||
return isset($map[$iso2]) ? $map[$iso2] : $iso2;
|
||||
}
|
||||
|
||||
protected function log($message)
|
||||
{
|
||||
PR_DHL()->log_msg('[INTERNETMARKE] '.$message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
use PR\DHL\REST_API\Parcel_DE\Item_Info;
|
||||
|
||||
class PR_DHL_API_Paket extends PR_DHL_API
|
||||
{
|
||||
const DHL_PAKET_DISPLAY_DAYS = 5;
|
||||
|
||||
const DHL_PAKET_REMOVE_DAYS = 2;
|
||||
|
||||
protected $dhl_my_account = null;
|
||||
|
||||
private $de_national_holidays = ['2020-11-22', '2020-11-29', '2020-12-06', '2020-12-13', '2020-12-20', '2020-12-25', '2020-12-26', '2020-12-27', '2021-01-01', '2021-04-02', '2021-04-04', '2021-04-05', '2021-05-01', '2021-05-13', '2021-05-23', '2021-05-24', '2021-10-03', '2021-12-25', '2021-12-26', '2022-01-01', '2022-04-15', '2022-04-17', '2022-04-18', '2022-05-01', '2022-05-26', '2022-06-05', '2022-06-06', '2022-10-03', '2022-12-25', '2022-12-26', '2023-01-01', '2023-04-07', '2023-04-09', '2023-04-10', '2023-05-01', '2023-05-18', '2023-05-28', '2023-05-29', '2023-10-03', '2023-12-25', '2023-12-26', '2024-01-01', '2024-03-29', '2024-03-31', '2024-04-01', '2024-05-01', '2024-05-09', '2024-05-19', '2024-05-20', '2024-10-03', '2024-12-25', '2024-12-26'];
|
||||
|
||||
public function __construct($country_code)
|
||||
{
|
||||
$this->country_code = $country_code;
|
||||
|
||||
$this->dhl_label = new PR_DHL_API_REST_Parcel_DE($country_code);
|
||||
$this->dhl_finder = new PR_DHL_API_REST_Finder;
|
||||
$this->dhl_my_account = new PR_DHL_API_REST_Parcel_DE_MyAccount;
|
||||
}
|
||||
|
||||
public function is_dhl_paket()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function maybe_sandbox($args)
|
||||
{
|
||||
|
||||
if (isset($args['sandbox']) && ($args['sandbox'] == 'yes')) {
|
||||
$sandbox_info = $this->sandbox_info();
|
||||
$args['api_user'] = $sandbox_info['username'];
|
||||
$args['api_pwd'] = $sandbox_info['pass'];
|
||||
$args['account_num'] = $sandbox_info['rest_api_account_no'];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
public function get_dhl_label($args)
|
||||
{
|
||||
$args['dhl_settings'] = $this->maybe_sandbox($args['dhl_settings']);
|
||||
|
||||
return parent::get_dhl_label($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multiple labels in 1 API call.
|
||||
*
|
||||
* @param array[] $multiple_orders_args Set of orders args.
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_dhl_labels($multiple_orders_args)
|
||||
{
|
||||
$items_info = [];
|
||||
$labels = [];
|
||||
$uom = get_option('woocommerce_weight_unit');
|
||||
|
||||
foreach ($multiple_orders_args as $args) {
|
||||
$args['dhl_settings'] = $this->maybe_sandbox($args['dhl_settings']);
|
||||
try {
|
||||
$this->dhl_label->set_arguments($args);
|
||||
$items_info[] = new Item_Info($args, $uom);
|
||||
} catch (Exception $e) {
|
||||
$labels['errors'][] = [
|
||||
'order_id' => $args['order_details']['order_id'],
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the item and get the barcode
|
||||
if (! empty($items_info)) {
|
||||
$items = $this->dhl_label->api_client->create_items($items_info);
|
||||
foreach ($items['items'] as $item) {
|
||||
$order_id = (int) str_replace(apply_filters('pr_shipping_dhl_paket_label_ref_no_prefix', 'order_'), '', $item->shipmentRefNo);
|
||||
if (isset($item->label->b64)) {
|
||||
$file = $this->dhl_label->save_data_file('label', $order_id, $item->label->b64);
|
||||
$labels['labels'][] = [
|
||||
'order_id' => $order_id,
|
||||
'label_path' => $file['label_path'],
|
||||
'tracking_number' => $item->shipmentNo,
|
||||
'tracking_status' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($items['errors'] as $error) {
|
||||
$labels['errors'][] = [
|
||||
'order_id' => $error['order_id'],
|
||||
'message' => $error['message'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
public function delete_dhl_label($args)
|
||||
{
|
||||
return parent::delete_dhl_label($this->maybe_sandbox($args));
|
||||
}
|
||||
|
||||
public function get_parcel_location($args)
|
||||
{
|
||||
$args['dhl_settings'] = $this->maybe_sandbox($args['dhl_settings']);
|
||||
|
||||
return parent::get_parcel_location($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the Deutsche Post settings.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array An associative array of the settings keys mapping to their values.
|
||||
*/
|
||||
public function get_settings()
|
||||
{
|
||||
return get_option('woocommerce_pr_dhl_paket_settings', []);
|
||||
}
|
||||
|
||||
public function set_dhl_myaccount_info($value)
|
||||
{
|
||||
update_option('woocommerce_pr_dhl_paket_settings', $value);
|
||||
}
|
||||
|
||||
public function get_dhl_myaccount_pwd_expiration()
|
||||
{
|
||||
return get_option('wc_dhl_myaccount_pwd_expiration', '');
|
||||
}
|
||||
|
||||
public function set_dhl_myaccount_pwd_expiration($value)
|
||||
{
|
||||
update_option('wc_dhl_myaccount_pwd_expiration', $value);
|
||||
}
|
||||
|
||||
public function get_dhl_booking_text()
|
||||
{
|
||||
return get_option('wc_dhl_booking_text', '');
|
||||
}
|
||||
|
||||
public function set_dhl_booking_text($value)
|
||||
{
|
||||
update_option('wc_dhl_booking_text', $value);
|
||||
}
|
||||
|
||||
public function get_dhl_products_international()
|
||||
{
|
||||
$country_code = $this->country_code;
|
||||
|
||||
$germany_int = [
|
||||
'V55PAK' => esc_html__('DHL Paket Connect', 'dhl-for-woocommerce'),
|
||||
'V54EPAK' => esc_html__('DHL Europaket (B2B)', 'dhl-for-woocommerce'),
|
||||
'V53WPAK' => esc_html__('DHL Paket International', 'dhl-for-woocommerce'),
|
||||
'V66WPI' => esc_html__('DHL Warenpost International', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
$dhl_prod_int = [];
|
||||
|
||||
switch ($country_code) {
|
||||
case 'DE':
|
||||
$dhl_prod_int = $germany_int;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return apply_filters('pr_shipping_dhl_paket_products_international', $dhl_prod_int);
|
||||
}
|
||||
|
||||
public function get_dhl_products_domestic()
|
||||
{
|
||||
$country_code = $this->country_code;
|
||||
|
||||
$germany_dom = [
|
||||
'V01PAK' => esc_html__('DHL Paket', 'dhl-for-woocommerce'),
|
||||
'V01PRIO' => esc_html__('DHL Paket PRIO', 'dhl-for-woocommerce'),
|
||||
'V62KP' => esc_html__('DHL Kleinpaket', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
$dhl_prod_dom = [];
|
||||
|
||||
switch ($country_code) {
|
||||
case 'DE':
|
||||
$dhl_prod_dom = $germany_dom;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return apply_filters('pr_shipping_dhl_paket_products_domestic', $dhl_prod_dom);
|
||||
}
|
||||
|
||||
public function get_dhl_preferred_day_time($postcode, $account_num, $cutoff_time = '12:00', $exclude_working_days = [])
|
||||
{
|
||||
// Always exclude Sunday
|
||||
$exclude_sun = ['Sun' => esc_html__('sun', 'dhl-for-woocommerce')];
|
||||
$exclude_working_days += $exclude_sun;
|
||||
$day_counter = 0;
|
||||
|
||||
// Get existing timezone to reset afterwards
|
||||
// $current_timzone = date_default_timezone_get();
|
||||
|
||||
// Always set and get DE timezone and check against it.
|
||||
// date_default_timezone_set('Europe/Berlin');
|
||||
// Get existing time locale
|
||||
// $current_locale = setlocale(LC_TIME, 0);
|
||||
// Set time locale based on WP locale setting (Settings->General)
|
||||
// $wp_locale = get_locale();
|
||||
// setlocale(LC_TIME, $wp_locale);
|
||||
// setlocale(LC_TIME, 'de_DE', 'deu_deu', 'de_DE.utf8', 'German', 'deu/ger', 'de_DE@euro', 'de', 'ge');
|
||||
|
||||
$tz_obj = new DateTimeZone('Europe/Berlin');
|
||||
$today = new DateTime('now', $tz_obj); // Should the order date be passed as a variable?
|
||||
$today_de_timestamp = $today->getTimestamp();
|
||||
|
||||
$week_day = $today->format('D');
|
||||
$week_date = $today->format('Y-m-d');
|
||||
$week_time = $today->format('H:i');
|
||||
|
||||
// Compare week day with key since key includes capital letter in beginning and will work for English AND German!
|
||||
// Check if today is a working day...
|
||||
if ((! array_key_exists($week_day, $exclude_working_days)) && (! in_array($week_date, $this->de_national_holidays))) {
|
||||
// ... and check if after cutoff time if today is a transfer day
|
||||
if ($today_de_timestamp >= strtotime($cutoff_time)) {
|
||||
// If the cut off time has been passed, then add a day
|
||||
$today->add(new DateInterval('P1D')); // Add 1 day
|
||||
$week_day = $today->format('D');
|
||||
$week_date = $today->format('Y-m-d');
|
||||
|
||||
$day_counter++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the next transfer days are working days
|
||||
while (array_key_exists($week_day, $exclude_working_days) || in_array($week_date, $this->de_national_holidays)) {
|
||||
$today->add(new DateInterval('P1D')); // Add 1 day
|
||||
$week_day = $today->format('D');
|
||||
$week_date = $today->format('Y-m-d');
|
||||
|
||||
$day_counter++;
|
||||
}
|
||||
|
||||
$args['postcode'] = $postcode;
|
||||
$args['start_date'] = $week_date;
|
||||
$settings = $this->get_settings();
|
||||
|
||||
if (isset($settings['dhl_sandbox']) && ($settings['dhl_sandbox'] == 'yes')) {
|
||||
$sandbox_info = $this->sandbox_info();
|
||||
$args['account_num'] = $sandbox_info['rest_api_account_no'];
|
||||
} else {
|
||||
$args['account_num'] = $account_num;
|
||||
}
|
||||
|
||||
$dhl_parcel_services = new PR_DHL_API_REST_Parcel;
|
||||
$preferred_services = $dhl_parcel_services->get_dhl_parcel_services($args);
|
||||
|
||||
$preferred_day_time = [];
|
||||
$preferred_day_time['preferred_day'] = $this->get_dhl_preferred_day($preferred_services);
|
||||
|
||||
// Reset time locael
|
||||
// setlocale(LC_TIME, $current_locale);
|
||||
// Reset timezone to not affect any other plugins
|
||||
// date_default_timezone_set($current_timzone);
|
||||
|
||||
return $preferred_day_time;
|
||||
}
|
||||
|
||||
protected function get_dhl_preferred_day($preferred_services)
|
||||
{
|
||||
$day_of_week_arr = [
|
||||
'1' => esc_html__('Mon', 'dhl-for-woocommerce'),
|
||||
'2' => esc_html__('Tue', 'dhl-for-woocommerce'),
|
||||
'3' => esc_html__('Wed', 'dhl-for-woocommerce'),
|
||||
'4' => esc_html__('Thu', 'dhl-for-woocommerce'),
|
||||
'5' => esc_html__('Fri', 'dhl-for-woocommerce'),
|
||||
'6' => esc_html__('Sat', 'dhl-for-woocommerce'),
|
||||
'7' => esc_html__('Sun', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
$preferred_days = [];
|
||||
if (isset($preferred_services->preferredDay->available) && $preferred_services->preferredDay->available && isset($preferred_services->preferredDay->validDays)) {
|
||||
|
||||
foreach ($preferred_services->preferredDay->validDays as $days_key => $days_value) {
|
||||
$temp_day_time = strtotime($days_value->start);
|
||||
|
||||
$day_of_week = gmdate('N', $temp_day_time);
|
||||
$week_date = gmdate('Y-m-d', $temp_day_time);
|
||||
|
||||
$preferred_days[$week_date] = $day_of_week_arr[$day_of_week];
|
||||
}
|
||||
|
||||
// Add none option
|
||||
array_unshift($preferred_days, esc_html__('none', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
return $preferred_days;
|
||||
}
|
||||
|
||||
public function get_dhl_duties()
|
||||
{
|
||||
$duties = parent::get_dhl_duties();
|
||||
|
||||
$duties_paket = [
|
||||
'DXV' => esc_html__('Delivery Duty Paid (excl. VAT )', 'dhl-for-woocommerce'),
|
||||
'DDX' => esc_html__('Delivery Duty Paid (excl. Duties, taxes and VAT)', 'dhl-for-woocommerce'),
|
||||
];
|
||||
$duties += $duties_paket;
|
||||
|
||||
return $duties;
|
||||
}
|
||||
|
||||
public function get_dhl_visual_age()
|
||||
{
|
||||
$visual_age = [
|
||||
'0' => _x('none', 'age context', 'dhl-for-woocommerce'),
|
||||
'A16' => esc_html__('Minimum age of 16', 'dhl-for-woocommerce'),
|
||||
'A18' => esc_html__('Minimum age of 18', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
return $visual_age;
|
||||
}
|
||||
|
||||
public function sandbox_info()
|
||||
{
|
||||
return [
|
||||
'username' => '2222222222_01',
|
||||
'pass' => 'pass',
|
||||
'account_no' => '2222222222',
|
||||
'rest_api_account_no' => '3333333333',
|
||||
];
|
||||
}
|
||||
|
||||
public function get_my_account()
|
||||
{
|
||||
return $this->dhl_my_account->get_dhl_my_account();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
class PR_DHL_API_REST_Finder extends PR_DHL_API_REST
|
||||
{
|
||||
/**
|
||||
* Request arguments.
|
||||
*/
|
||||
private array $args = [];
|
||||
|
||||
/**
|
||||
* The URL to the API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
public function get_parcel_location($args)
|
||||
{
|
||||
try {
|
||||
|
||||
$this->set_arguments($args);
|
||||
|
||||
$this->set_endpoint('location-finder/v1/find-by-address');
|
||||
$this->set_query_string();
|
||||
|
||||
// PR_DHL()->log_msg( '"get_parcel_location" called with: ' . print_r( $args , true ) );
|
||||
|
||||
$response_body = $this->get_request();
|
||||
|
||||
// PR_DHL()->log_msg( 'Response: Successful');
|
||||
|
||||
return $response_body;
|
||||
} catch (Exception $e) {
|
||||
PR_DHL()->log_msg('Response Error: '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function set_arguments($args)
|
||||
{
|
||||
// Validate set args
|
||||
|
||||
if (empty($args['shipping_address']['city']) && empty($args['shipping_address']['postcode'])) {
|
||||
throw new Exception(esc_html__('Shipping "City" and "Postcode" are empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['shipping_address']['country'])) {
|
||||
throw new Exception(esc_html__('Shipping "Country" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
protected function set_query_string()
|
||||
{
|
||||
$packstation = ($this->args['dhl_packstation_filter'] == 'true');
|
||||
$branch = ($this->args['dhl_branch_filter'] == 'true');
|
||||
$country = $this->args['shipping_address']['country'];
|
||||
|
||||
if ($packstation && $branch) {
|
||||
$serviceType = 'parcel:pick-up-all';
|
||||
} elseif ($branch) {
|
||||
$serviceType = 'parcel:pick-up';
|
||||
} elseif ($packstation) {
|
||||
$serviceType = ($country == 'DE') ? 'parcel:pick-up-registered' : 'parcel:pick-up-unregistered';
|
||||
}
|
||||
|
||||
if (! isset($serviceType)) {
|
||||
throw new Exception(esc_html__('No service type selected', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$finder_query_string = [
|
||||
'radius' => 2000, // in meters
|
||||
'limit' => ($this->args['dhl_parcel_limit']) ? $this->args['dhl_parcel_limit'] : 10,
|
||||
'serviceType' => $serviceType,
|
||||
'streetAddress' => $this->args['shipping_address']['address'],
|
||||
'zipCode' => $this->args['shipping_address']['postcode'],
|
||||
'addressLocality' => $this->args['shipping_address']['city'],
|
||||
'countryCode' => $this->args['shipping_address']['country'],
|
||||
];
|
||||
|
||||
$this->query_string = http_build_query($finder_query_string);
|
||||
}
|
||||
|
||||
protected function set_header($authorization = '')
|
||||
{
|
||||
$dhl_header['Accept'] = 'application/json';
|
||||
$dhl_header['DHL-API-Key'] = $this->get_api_key();
|
||||
|
||||
if (! empty($authorization)) {
|
||||
$dhl_header['Authorization'] = $authorization;
|
||||
}
|
||||
|
||||
$this->remote_header = $dhl_header;
|
||||
}
|
||||
|
||||
public function get_request()
|
||||
{
|
||||
|
||||
$rest_auth = '';
|
||||
$api_url = $this->get_api_url();
|
||||
$api_url = trailingslashit($api_url);
|
||||
|
||||
$this->set_header();
|
||||
|
||||
$wp_request_url = $api_url.$this->get_endpoint().'?'.$this->get_query_string();
|
||||
$wp_request_headers = $this->get_header();
|
||||
|
||||
PR_DHL()->log_msg('GET URL: '.$wp_request_url);
|
||||
|
||||
$wp_dhl_rest_response = wp_remote_get(
|
||||
$wp_request_url,
|
||||
[
|
||||
'headers' => $wp_request_headers,
|
||||
'timeout' => self::WP_POST_TIMEOUT,
|
||||
]
|
||||
);
|
||||
|
||||
// PR_DHL()->log_msg( 'GET Request Headers: ' . print_r( $wp_request_headers, true ) );
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($wp_dhl_rest_response);
|
||||
$response_body = json_decode(wp_remote_retrieve_body($wp_dhl_rest_response));
|
||||
|
||||
PR_DHL()->log_msg('GET Response Code: '.$response_code);
|
||||
// PR_DHL()->log_msg( 'GET Response Body: ' . print_r( $response_body, true ) );
|
||||
|
||||
switch ($response_code) {
|
||||
case '200':
|
||||
case '201':
|
||||
break;
|
||||
case '400':
|
||||
$error_message = ($response_body->detail) ? $response_body->detail : '';
|
||||
throw new Exception(esc_html__('400 - ', 'dhl-for-woocommerce').esc_html($error_message));
|
||||
break;
|
||||
case '401':
|
||||
throw new Exception(esc_html__('401 - Unauthorized Access - Invalid token or Authentication Header parameter', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '408':
|
||||
throw new Exception(esc_html__('408 - Request Timeout', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '429':
|
||||
throw new Exception(esc_html__('429 - Too many requests in given amount of time', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
case '503':
|
||||
throw new Exception(esc_html__('503 - Service Unavailable', 'dhl-for-woocommerce'));
|
||||
break;
|
||||
default:
|
||||
if (empty($response_body->detail)) {
|
||||
$error_message = esc_html__('GET error or timeout occured. Please try again later.', 'dhl-for-woocommerce');
|
||||
} else {
|
||||
$error_message = str_replace('/', ' / ', $response_body->detail);
|
||||
}
|
||||
|
||||
PR_DHL()->log_msg('GET Error: '.$response_code.' - '.$error_message);
|
||||
|
||||
throw new Exception(esc_html($response_code).' - '.esc_html($error_message));
|
||||
break;
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API URL.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_api_url()
|
||||
{
|
||||
$api_url = defined('PR_DHL_GLOBAL_URL') ? PR_DHL_GLOBAL_URL : '';
|
||||
|
||||
return $api_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API KEY.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_api_key()
|
||||
{
|
||||
$api_key = defined('PR_DHL_GLOBAL_API') ? PR_DHL_GLOBAL_API : '';
|
||||
|
||||
return $api_key;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
class PR_DHL_API_REST_Label extends PR_DHL_API_REST implements PR_DHL_API_Label
|
||||
{
|
||||
private $dhl_label_format = 'PDF';
|
||||
|
||||
private $dhl_label_size = '4x6'; // must be lowercase 'x'
|
||||
|
||||
private $dhl_label_page = 'A4';
|
||||
|
||||
private $dhl_label_layout = '1x1';
|
||||
|
||||
// const PR_DHL_LABEL_SIZE =
|
||||
// const PR_DHL_PAGE_SIZE =
|
||||
// const PR_DHL_LAYOUT =
|
||||
const PR_DHL_AUTO_CLOSE = '1';
|
||||
|
||||
private $args = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
try {
|
||||
|
||||
parent::__construct();
|
||||
// Set Endpoint
|
||||
$this->set_endpoint('/shipping/v1/label');
|
||||
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_dhl_label($args)
|
||||
{
|
||||
$this->set_arguments($args);
|
||||
$this->set_query_string();
|
||||
|
||||
$response_body = $this->post_request($args['dhl_settings']['dhl_api_key'], $args['dhl_settings']['dhl_api_secret']);
|
||||
|
||||
// This will work on one order but NOT on bulk!
|
||||
$label_response = $response_body->shipments[0]->packages[0]->responseDetails;
|
||||
$package_id = $label_response->labelDetails[0]->packageId;
|
||||
|
||||
$label_tracking_info = $this->save_label_file($package_id, $label_response->labelDetails[0]->format, $label_response->labelDetails[0]->labelData);
|
||||
|
||||
$label_tracking_info['tracking_number'] = $package_id;
|
||||
$label_tracking_info['tracking_status'] = isset($label_response->trackingNumberStatus) ? $label_response->trackingNumberStatus : '';
|
||||
|
||||
return $label_tracking_info;
|
||||
}
|
||||
|
||||
public function delete_dhl_label($args)
|
||||
{
|
||||
$upload_path = wp_upload_dir();
|
||||
$label_path = str_replace($upload_path['url'], $upload_path['path'], $args['label_url']);
|
||||
|
||||
if (file_exists($label_path)) {
|
||||
if (! is_writable($label_path)) {
|
||||
throw new Exception(esc_html__('DHL Label file is not writable!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
wp_delete_file($label_path);
|
||||
} else {
|
||||
throw new Exception(esc_html__('DHL Label could not be deleted!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
public function dhl_test_connection($client_id, $client_secret)
|
||||
{
|
||||
return $this->get_access_token($client_id, $client_secret);
|
||||
}
|
||||
|
||||
public function dhl_validate_field($key, $value)
|
||||
{
|
||||
$this->validate_field($key, $value);
|
||||
}
|
||||
|
||||
protected function validate_field($key, $value)
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
switch ($key) {
|
||||
case 'weight':
|
||||
$this->validate($value);
|
||||
break;
|
||||
case 'hs_code':
|
||||
$this->validate($value, 'string', 4, 20);
|
||||
break;
|
||||
default:
|
||||
parent::validate_field($key, $value);
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function save_label_file($package_id, $format, $label_data)
|
||||
{
|
||||
$label_name = 'dhl-label-'.$package_id.'.'.$format;
|
||||
$label_path = PR_DHL()->get_dhl_label_folder_dir().$label_name;
|
||||
$label_url = PR_DHL()->get_dhl_label_folder_url().$label_name;
|
||||
|
||||
if (validate_file($label_path) > 0) {
|
||||
throw new Exception(esc_html__('Invalid file path!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$label_data_decoded = base64_decode($label_data);
|
||||
$file_ret = file_put_contents($label_path, $label_data_decoded);
|
||||
|
||||
if (empty($file_ret)) {
|
||||
throw new Exception(esc_html__('DHL Label file cannot be saved!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
return [
|
||||
'label_url' => $label_url,
|
||||
'label_path' => $label_path,
|
||||
];
|
||||
}
|
||||
|
||||
protected function set_arguments($args)
|
||||
{
|
||||
// Validate set args
|
||||
|
||||
if (empty($args['dhl_settings']['dhl_api_key'])) {
|
||||
throw new Exception(esc_html__('Please, provide the username in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['dhl_api_secret'])) {
|
||||
throw new Exception(esc_html__('Please, provide the password for the username in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Validate order details
|
||||
if (empty($args['dhl_settings']['pickup'])) {
|
||||
throw new Exception(esc_html__('Please, provide a pickup account in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['distribution'])) {
|
||||
throw new Exception(esc_html__('Please, provide a distribution center in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (! empty($args['dhl_settings']['label_format'])) {
|
||||
$this->dhl_label_format = $args['dhl_settings']['label_format'];
|
||||
}
|
||||
|
||||
if (! empty($args['dhl_settings']['label_size'])) {
|
||||
$this->dhl_label_size = $args['dhl_settings']['label_size'];
|
||||
}
|
||||
|
||||
if (! empty($args['dhl_settings']['label_page'])) {
|
||||
$this->dhl_label_page = $args['dhl_settings']['label_page'];
|
||||
|
||||
if ($this->dhl_label_page == 'A4') {
|
||||
$this->dhl_label_layout = '4x1';
|
||||
} else {
|
||||
$this->dhl_label_layout = '1x1';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['dhl_product'])) {
|
||||
throw new Exception(esc_html__('DHL "Product" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['order_id'])) {
|
||||
throw new Exception(esc_html__('Shop "Order ID" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['weightUom'])) {
|
||||
throw new Exception(esc_html__('Shop "Weight Units of Measure" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['weight'])) {
|
||||
throw new Exception(esc_html__('Order "Weight" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Validate weight
|
||||
try {
|
||||
$this->validate_field('weight', $args['order_details']['weight']);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Weight - '.esc_html($e->getMessage()));
|
||||
}
|
||||
|
||||
// if ( empty( $args['order_details']['duties'] )) {
|
||||
// throw new Exception( esc_html__( 'DHL "Duties" is empty!', 'dhl-for-woocommerce' ) );
|
||||
// }
|
||||
|
||||
if (empty($args['order_details']['currency'])) {
|
||||
throw new Exception(esc_html__('Shop "Currency" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Validate shipping address
|
||||
if (empty($args['shipping_address']['address_1'])) {
|
||||
throw new Exception(esc_html__('Shipping "Address 1" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['shipping_address']['city'])) {
|
||||
throw new Exception(esc_html__('Shipping "City" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['shipping_address']['country'])) {
|
||||
throw new Exception(esc_html__('Shipping "Country" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Add default values for required fields that might not be passed e.g. phone
|
||||
$default_args = [
|
||||
'shipping_address' => [
|
||||
'name' => '',
|
||||
'company' => '',
|
||||
'address_2' => '',
|
||||
'email' => '',
|
||||
// 'idNumber' => '',
|
||||
// 'idType' => '',
|
||||
'postcode' => '',
|
||||
'state' => '',
|
||||
'phone' => ' ',
|
||||
],
|
||||
'order_details' => [
|
||||
'cod_value' => 0,
|
||||
'dangerous_goods' => '',
|
||||
],
|
||||
];
|
||||
|
||||
$args['shipping_address'] = wp_parse_args($args['shipping_address'], $default_args['shipping_address']);
|
||||
$args['order_details'] = wp_parse_args($args['order_details'], $default_args['order_details']);
|
||||
|
||||
$default_args_item = [
|
||||
'item_description' => '',
|
||||
'sku' => '',
|
||||
'item_value' => 0,
|
||||
'country_origin' => '',
|
||||
'hs_code' => '',
|
||||
'qty' => 1,
|
||||
];
|
||||
|
||||
foreach ($args['items'] as $key => $item) {
|
||||
|
||||
if (! empty($item['hs_code'])) {
|
||||
try {
|
||||
$this->validate_field('hs_code', $item['hs_code']);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('HS Code - '.esc_html($e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$args['items'][$key] = wp_parse_args($item, $default_args_item);
|
||||
}
|
||||
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
protected function set_query_string()
|
||||
{
|
||||
$dhl_label_query_string =
|
||||
[
|
||||
'format' => $this->dhl_label_format,
|
||||
'labelSize' => $this->dhl_label_size,
|
||||
'pageSize' => $this->dhl_label_page,
|
||||
'layout' => $this->dhl_label_layout,
|
||||
'autoClose' => self::PR_DHL_AUTO_CLOSE,
|
||||
];
|
||||
|
||||
$this->query_string = http_build_query($dhl_label_query_string);
|
||||
}
|
||||
|
||||
protected function set_message()
|
||||
{
|
||||
|
||||
if (! empty($this->args)) {
|
||||
|
||||
$package_id = '';
|
||||
if (! empty($this->args['order_details']['prefix'])) {
|
||||
$package_id = $this->args['order_details']['prefix'];
|
||||
}
|
||||
$package_id .= $this->args['order_details']['order_id'].time();
|
||||
// Package id must be max 30
|
||||
$package_id = substr($package_id, 0, 30);
|
||||
|
||||
$package_desc = $package_id;
|
||||
if (! empty($this->args['order_details']['description'])) {
|
||||
$package_desc = mb_substr($this->args['order_details']['description'], 0, 50, 'UTF-8');
|
||||
}
|
||||
|
||||
if (strlen($this->args['shipping_address']['address_1']) > 50) {
|
||||
$consignee_address_1 = mb_substr($this->args['shipping_address']['address_1'], 0, 50, 'UTF-8');
|
||||
|
||||
$this->args['shipping_address']['address_2'] = mb_substr($this->args['shipping_address']['address_1'], 50, 'UTF-8').' '.$this->args['shipping_address']['address_2'];
|
||||
|
||||
} else {
|
||||
$consignee_address_1 = $this->args['shipping_address']['address_1'];
|
||||
}
|
||||
|
||||
$consignee_address_3 = '';
|
||||
if (strlen($this->args['shipping_address']['address_2']) > 50) {
|
||||
$consignee_address_2 = mb_substr($this->args['shipping_address']['address_2'], 0, 50, 'UTF-8');
|
||||
|
||||
$consignee_address_3 = mb_substr($this->args['shipping_address']['address_2'], 50, 50, 'UTF-8');
|
||||
} else {
|
||||
$consignee_address_2 = $this->args['shipping_address']['address_2'];
|
||||
}
|
||||
|
||||
$shipping_state = '';
|
||||
if ($this->args['shipping_address']['state']) {
|
||||
|
||||
// If China
|
||||
if ($this->args['shipping_address']['country'] == 'CN') {
|
||||
|
||||
// Remove everything after '/'
|
||||
$state_arr = explode('/', $this->args['shipping_address']['state']);
|
||||
|
||||
if ($state_arr) {
|
||||
$this->args['shipping_address']['state'] = trim($state_arr[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$shipping_state = mb_substr($this->args['shipping_address']['state'], 0, 20, 'UTF-8');
|
||||
}
|
||||
|
||||
$cod_value = 0;
|
||||
if (isset($this->args['order_details']['is_cod']) && ($this->args['order_details']['is_cod'] == 'yes')) {
|
||||
|
||||
$cod_value = round(floatval($this->args['order_details']['total_value']), 2);
|
||||
}
|
||||
|
||||
$dhl_label_body =
|
||||
[
|
||||
'shipments' => [
|
||||
[
|
||||
'pickupAccount' => $this->args['dhl_settings']['pickup'],
|
||||
'distributionCenter' => $this->args['dhl_settings']['distribution'],
|
||||
'consignmentNumber' => $this->args['dhl_settings']['handover'],
|
||||
'packages' => [
|
||||
[
|
||||
'consigneeAddress' => [
|
||||
'name' => $this->args['shipping_address']['name'],
|
||||
'companyName' => $this->args['shipping_address']['company'],
|
||||
'address1' => $consignee_address_1,
|
||||
'address2' => $consignee_address_2,
|
||||
'address3' => $consignee_address_3,
|
||||
'city' => $this->args['shipping_address']['city'],
|
||||
'postalCode' => $this->args['shipping_address']['postcode'],
|
||||
'state' => $shipping_state,
|
||||
'country' => $this->args['shipping_address']['country'],
|
||||
'phone' => $this->args['shipping_address']['phone'],
|
||||
],
|
||||
'packageDetails' => [
|
||||
'codAmount' => $cod_value,
|
||||
'currency' => $this->args['order_details']['currency'],
|
||||
'dgCategory' => $this->args['order_details']['dangerous_goods'],
|
||||
'orderedProduct' => $this->args['order_details']['dhl_product'],
|
||||
'packageDesc' => $package_desc,
|
||||
'packageId' => $package_id,
|
||||
'weight' => round(floatval($this->args['order_details']['weight']), 2),
|
||||
'weightUom' => strtoupper($this->args['order_details']['weightUom']),
|
||||
'billingRef1' => mb_substr($this->args['order_details']['order_note'], 0, 50, 'UTF-8'),
|
||||
'billingRef2' => mb_substr($this->args['order_details']['order_note'], 50, 25, 'UTF-8'),
|
||||
],
|
||||
// 'customsDetails' => $customsDetails
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Add customs info
|
||||
if (PR_DHL()->is_crossborder_shipment($this->args['shipping_address'])) {
|
||||
|
||||
$customsDetails = [];
|
||||
foreach ($this->args['items'] as $key => $item) {
|
||||
|
||||
$json_item = [
|
||||
'itemDescription' => mb_substr($item['item_description'], 0, 200, 'UTF-8'),
|
||||
'descriptionExport' => mb_substr($item['item_export'], 0, 200, 'UTF-8'),
|
||||
// 'descriptionImport' => substr( $item['item_description'], 0, 200 ),
|
||||
'countryOfOrigin' => $item['country_origin'],
|
||||
'hsCode' => $item['hs_code'],
|
||||
'packagedQuantity' => intval($item['qty']),
|
||||
'itemValue' => round(floatval($item['item_value']), 2),
|
||||
'skuNumber' => $item['sku'],
|
||||
];
|
||||
|
||||
array_push($customsDetails, $json_item);
|
||||
}
|
||||
// Add customs info
|
||||
$dhl_label_body['shipments'][0]['packages'][0]['customsDetails'] = $customsDetails;
|
||||
|
||||
// Add duties info
|
||||
$dhl_label_body['shipments'][0]['packages'][0]['packageDetails']['dutiesPaid'] = $this->args['order_details']['duties'];
|
||||
|
||||
// Declared info
|
||||
$dhl_label_body['shipments'][0]['packages'][0]['packageDetails']['declaredValue'] = round(floatval($this->args['order_details']['items_value']), 2);
|
||||
|
||||
}
|
||||
|
||||
// Unset/remove any items that are empty strings or 0, even if required!
|
||||
$dhl_label_body = $this->walk_recursive_remove($dhl_label_body);
|
||||
|
||||
$this->body_request = wp_json_encode($dhl_label_body, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
||||
// Unset/remove any items that are empty strings or 0
|
||||
private function walk_recursive_remove(array $array)
|
||||
{
|
||||
foreach ($array as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
$array[$k] = $this->walk_recursive_remove($v);
|
||||
}
|
||||
|
||||
if (empty($v)) {
|
||||
unset($array[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
<?php
|
||||
|
||||
use PR\DHL\REST_API\Drivers\JSON_API_Driver;
|
||||
use PR\DHL\REST_API\Drivers\Logging_Driver;
|
||||
use PR\DHL\REST_API\Drivers\WP_API_Driver;
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Paket\Auth;
|
||||
use PR\DHL\REST_API\Paket\Client;
|
||||
use PR\DHL\REST_API\Paket\Pickup_Request_Info;
|
||||
|
||||
// Exit if accessed directly or class already exists
|
||||
if (! defined('ABSPATH') || class_exists('PR_DHL_API_REST_Paket', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class PR_DHL_API_REST_Paket extends PR_DHL_API
|
||||
{
|
||||
/**
|
||||
* The URL to the API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const API_URL_PRODUCTION = 'https://api-eu.dhl.com/parcel/de/transportation/pickup/v3';
|
||||
|
||||
/**
|
||||
* The URL to the sandbox API.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
const API_URL_SANDBOX = 'https://api-sandbox.dhl.com/parcel/de/transportation/pickup/v3';
|
||||
|
||||
/**
|
||||
* The API driver instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
public $api_driver;
|
||||
|
||||
/**
|
||||
* The API authorization instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var Auth
|
||||
*/
|
||||
public $api_auth;
|
||||
|
||||
/**
|
||||
* The API client instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @var Client
|
||||
*/
|
||||
public $api_client;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $country_code The country code.
|
||||
*
|
||||
* @throws Exception If an error occurred while creating the API driver, auth or client.
|
||||
*/
|
||||
public function __construct($country_code)
|
||||
{
|
||||
$this->country_code = $country_code;
|
||||
|
||||
try {
|
||||
$this->api_driver = $this->create_api_driver();
|
||||
$this->api_auth = $this->create_api_auth();
|
||||
$this->api_client = $this->create_api_client();
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API client instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return Client
|
||||
*
|
||||
* @throws Exception If failed to create the API client.
|
||||
*/
|
||||
protected function create_api_client()
|
||||
{
|
||||
// Create the API client, using this instance's driver and auth objects
|
||||
return new Client(
|
||||
$this->get_customer_portal_username(),
|
||||
$this->get_customer_portal_password(),
|
||||
$this->get_api_url(),
|
||||
$this->api_driver,
|
||||
$this->api_auth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API driver instance.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return API_Driver_Interface
|
||||
*
|
||||
* @throws Exception If failed to create the API driver.
|
||||
*/
|
||||
protected function create_api_driver()
|
||||
{
|
||||
// Use a standard WordPress-driven API driver to send requests using WordPress' functions
|
||||
$driver = new WP_API_Driver;
|
||||
|
||||
// This will log requests given to the original driver and log responses returned from it
|
||||
$driver = new Logging_Driver(PR_DHL(), $driver);
|
||||
|
||||
// This will prepare requests given to the previous driver for JSON content
|
||||
// and parse responses returned from it as JSON.
|
||||
$driver = new JSON_API_Driver($driver);
|
||||
|
||||
// , decorated using the JSON driver decorator class
|
||||
return $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API auth instance.
|
||||
*
|
||||
* @return API_Auth_Interface
|
||||
*
|
||||
* @throws Exception If failed to create the API auth.
|
||||
*/
|
||||
protected function create_api_auth()
|
||||
{
|
||||
// Get the saved DHL customer API credentials
|
||||
[$username, $password] = $this->get_api_creds();
|
||||
|
||||
// Create the auth object using this instance's API driver and URL
|
||||
return new Auth(
|
||||
$this->api_driver,
|
||||
$this->get_api_url(),
|
||||
$username,
|
||||
$password,
|
||||
$this->get_api_key(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function is_dhl_paket()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API URL.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If failed to determine if using the sandbox API or not.
|
||||
*/
|
||||
public function get_api_url()
|
||||
{
|
||||
$is_sandbox = $this->get_setting('dhl_sandbox');
|
||||
$is_sandbox = filter_var($is_sandbox, FILTER_VALIDATE_BOOLEAN);
|
||||
$api_url = ($is_sandbox) ? static::API_URL_SANDBOX : static::API_URL_PRODUCTION;
|
||||
|
||||
return $api_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API credentials.
|
||||
*
|
||||
* @return array The client ID and client secret.
|
||||
*
|
||||
* @throws Exception If failed to retrieve the API credentials.
|
||||
*/
|
||||
public function get_api_creds()
|
||||
{
|
||||
$customer_portal_login = $this->get_customer_portal_login();
|
||||
|
||||
return [
|
||||
$customer_portal_login['username'],
|
||||
$customer_portal_login['pass'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Customer Portal login credentials.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array The customer username and password for Business portal API calls.
|
||||
*/
|
||||
public function get_customer_portal_login()
|
||||
{
|
||||
$is_sandbox = $this->get_setting('dhl_sandbox');
|
||||
$is_sandbox = filter_var($is_sandbox, FILTER_VALIDATE_BOOLEAN);
|
||||
if ($is_sandbox) {
|
||||
$sandbox = $this->sandbox_info_customer_portal();
|
||||
|
||||
return [
|
||||
'username' => $sandbox['username'],
|
||||
'pass' => $sandbox['pass'],
|
||||
];
|
||||
|
||||
} else {
|
||||
return [
|
||||
'username' => $this->get_setting('dhl_api_user'),
|
||||
'pass' => $this->get_setting('dhl_api_pwd'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function get_customer_portal_username()
|
||||
{
|
||||
$customer_login = $this->get_customer_portal_login();
|
||||
|
||||
return $customer_login['username'];
|
||||
}
|
||||
|
||||
public function get_customer_portal_password()
|
||||
{
|
||||
$customer_login = $this->get_customer_portal_login();
|
||||
|
||||
return $customer_login['pass'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Sandbox DHL Pickup Account Number
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If failed to retrieve the EKP from the settings.
|
||||
*/
|
||||
public function maybe_get_sandbox_account_number()
|
||||
{
|
||||
$is_sandbox = $this->get_setting('dhl_sandbox');
|
||||
$is_sandbox = filter_var($is_sandbox, FILTER_VALIDATE_BOOLEAN);
|
||||
if ($is_sandbox) {
|
||||
$sandbox_info = $this->sandbox_info_customer_portal();
|
||||
|
||||
return $sandbox_info['account_no'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single setting.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @param string $key The key of the setting to retrieve.
|
||||
* @param string $default The value to return if the setting is not saved.
|
||||
* @return mixed The setting value.
|
||||
*/
|
||||
public function get_setting($key, $default = '')
|
||||
{
|
||||
$settings = $this->get_settings();
|
||||
|
||||
return isset($settings[$key]) ? $settings[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the Deutsche Post settings.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return array An associative array of the settings keys mapping to their values.
|
||||
*/
|
||||
public function get_settings()
|
||||
{
|
||||
return get_option('woocommerce_pr_dhl_paket_settings', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function dhl_test_connection($client_id, $client_secret)
|
||||
{
|
||||
// try {
|
||||
// Test the given ID and secret
|
||||
// $token = $this->api_auth->test_connection( $client_id, $client_secret );
|
||||
// Save the token if successful
|
||||
// $this->api_auth->save_token( $token );
|
||||
//
|
||||
// return $token;
|
||||
// } catch ( Exception $e ) {
|
||||
// $this->api_auth->save_token( null );
|
||||
// throw $e;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function dhl_reset_connection()
|
||||
{
|
||||
// return $this->api_auth->revoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function get_dhl_products_domestic()
|
||||
{
|
||||
$country_code = $this->country_code;
|
||||
|
||||
$germany_dom = [
|
||||
'V01PAK' => esc_html__('DHL Paket', 'dhl-for-woocommerce'),
|
||||
'V01PRIO' => esc_html__('DHL Paket PRIO', 'dhl-for-woocommerce'),
|
||||
'V62KP' => esc_html__('DHL Kleinpaket', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
$dhl_prod_dom = [];
|
||||
|
||||
switch ($country_code) {
|
||||
case 'DE':
|
||||
$dhl_prod_dom = $germany_dom;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return apply_filters('pr_shipping_dhl_paket_products_domestic', $dhl_prod_dom);
|
||||
}
|
||||
|
||||
public function get_dhl_products_international()
|
||||
{
|
||||
$country_code = $this->country_code;
|
||||
|
||||
$germany_int = [
|
||||
'V55PAK' => esc_html__('DHL Paket Connect', 'dhl-for-woocommerce'),
|
||||
'V54EPAK' => esc_html__('DHL Europaket (B2B)', 'dhl-for-woocommerce'),
|
||||
'V53WPAK' => esc_html__('DHL Paket International', 'dhl-for-woocommerce'),
|
||||
'V66WPI' => esc_html__('DHL Warenpost International', 'dhl-for-woocommerce'),
|
||||
];
|
||||
|
||||
$dhl_prod_int = [];
|
||||
|
||||
switch ($country_code) {
|
||||
case 'DE':
|
||||
$dhl_prod_int = $germany_int;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return apply_filters('pr_shipping_dhl_paket_products_international', $dhl_prod_int);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*/
|
||||
public function request_dhl_pickup($args, $forcePortalPickupAddressMatch = true)
|
||||
{
|
||||
$uom = get_option('woocommerce_weight_unit');
|
||||
|
||||
// Maybe override account billing number here for Sandbox user
|
||||
if ($this->maybe_get_sandbox_account_number()) {
|
||||
$args['dhl_pickup_billing_number'] = $this->maybe_get_sandbox_account_number();
|
||||
}
|
||||
|
||||
try {
|
||||
$request_pickup_info = new Pickup_Request_Info($args, $uom);
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Verify pickup address with DHL portal pickup address first
|
||||
if ($forcePortalPickupAddressMatch) {
|
||||
$postalCode = $request_pickup_info->pickup_address['postalCode'];
|
||||
$localAddress = $request_pickup_info->pickup_address;
|
||||
|
||||
try {
|
||||
$pickup_location_response = $this->api_client->get_pickup_location($postalCode);
|
||||
|
||||
$foundPickupLocMatch = false;
|
||||
foreach ($pickup_location_response as $pickup_address) {
|
||||
$portalAddress = $pickup_address->pickupAddress ?? null;
|
||||
if (strtolower(preg_replace('/[^A-Za-z0-9]/', '', $portalAddress->addressStreet)) == strtolower(preg_replace('/[^A-Za-z0-9]/', '', $localAddress['addressStreet']))
|
||||
&& strtolower(preg_replace('/[^A-Za-z0-9]/', '', $portalAddress->addressHouse)) == strtolower(preg_replace('/[^A-Za-z0-9]/', '', $localAddress['addressHouse']))
|
||||
&& strtolower(preg_replace('/[^A-Za-z0-9]/', '', $portalAddress->city)) == strtolower(preg_replace('/[^A-Za-z0-9]/', '', $localAddress['city']))
|
||||
&& strtolower(preg_replace('/[^A-Za-z0-9]/', '', $portalAddress->postalCode)) == strtolower(preg_replace('/[^A-Za-z0-9]/', '', $localAddress['postalCode']))
|
||||
) {
|
||||
$foundPickupLocMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $foundPickupLocMatch) {
|
||||
throw new Exception(
|
||||
esc_html__('Your Shipper Address must match a Pickup address on your DHL Portal.', 'dhl-for-woocommerce')
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the shipping label
|
||||
try {
|
||||
$request_pickup_response = $this->api_client->create_pickup_request($request_pickup_info);
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $request_pickup_response;
|
||||
}
|
||||
|
||||
public function sandbox_info_customer_portal()
|
||||
{
|
||||
return [
|
||||
'username' => 'user-valid',
|
||||
'pass' => 'SandboxPasswort2023!',
|
||||
'account_no' => '22222222220801',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API KEY.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_api_key()
|
||||
{
|
||||
$api_key = defined('PR_DHL_GLOBAL_API') ? PR_DHL_GLOBAL_API : '';
|
||||
|
||||
return $api_key;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Auth_Interface;
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Parcel_DE_MyAccount\Auth;
|
||||
use PR\DHL\REST_API\Parcel_DE_MyAccount\Client;
|
||||
|
||||
// Exit if accessed directly or class already exists
|
||||
if (! defined('ABSPATH') || class_exists('PR_DHL_API_Parcel_DE', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class PR_DHL_API_REST_Parcel_DE_MyAccount extends PR_DHL_API_REST_Parcel_DE
|
||||
{
|
||||
/**
|
||||
* The URL to the API.
|
||||
*/
|
||||
const API_URL_PRODUCTION = 'https://api-eu.dhl.com/parcel/de/account/';
|
||||
|
||||
/**
|
||||
* The URL to the sandbox API.
|
||||
*/
|
||||
const API_URL_SANDBOX = 'https://api-sandbox.dhl.com/parcel/de/account/';
|
||||
|
||||
/**
|
||||
* The API driver instance.
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
public $api_driver;
|
||||
|
||||
/**
|
||||
* The API authorization instance.
|
||||
*
|
||||
* @var Auth
|
||||
*/
|
||||
public $api_auth;
|
||||
|
||||
/**
|
||||
* The API client instance.
|
||||
*
|
||||
* @var Client
|
||||
*/
|
||||
public $api_client;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @throws Exception If an error occurred while creating the API driver, auth or client.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
try {
|
||||
$this->api_driver = $this->create_api_driver();
|
||||
$this->api_auth = $this->create_api_auth();
|
||||
$this->api_client = $this->create_api_client();
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API client instance.
|
||||
*
|
||||
* @return Client
|
||||
*
|
||||
* @throws Exception If failed to create the API client.
|
||||
*/
|
||||
protected function create_api_client()
|
||||
{
|
||||
// Create the API client, using this instance's driver and auth objects
|
||||
return new Client(
|
||||
$this->get_api_url(),
|
||||
$this->api_driver,
|
||||
$this->api_auth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API auth instance.
|
||||
*
|
||||
* @return API_Auth_Interface
|
||||
*
|
||||
* @throws Exception If failed to create the API auth.
|
||||
*/
|
||||
protected function create_api_auth()
|
||||
{
|
||||
// Get the saved DHL customer API credentials
|
||||
[$username, $password] = $this->get_api_creds();
|
||||
|
||||
// Create the auth object using this instance's API driver and URL
|
||||
return new Auth(
|
||||
$this->api_driver,
|
||||
$this->get_api_url(),
|
||||
$this->get_api_key(),
|
||||
$this->get_api_secret(),
|
||||
$username,
|
||||
$password,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API URL.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception If failed to determine if using the sandbox API or not.
|
||||
*/
|
||||
public function get_api_url()
|
||||
{
|
||||
$is_sandbox = $this->get_setting('dhl_sandbox');
|
||||
$is_sandbox = filter_var($is_sandbox, FILTER_VALIDATE_BOOLEAN);
|
||||
$api_url = ($is_sandbox) ? static::API_URL_SANDBOX : static::API_URL_PRODUCTION;
|
||||
|
||||
return $api_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API credentials.
|
||||
*
|
||||
* @return array The client ID and client secret.
|
||||
*
|
||||
* @throws Exception If failed to retrieve the API credentials.
|
||||
*/
|
||||
public function get_api_creds()
|
||||
{
|
||||
$customer_portal_login = $this->get_customer_portal_login();
|
||||
|
||||
return [
|
||||
$customer_portal_login['username'],
|
||||
$customer_portal_login['pass'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* API sandbox creds.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sandbox_info_customer_portal()
|
||||
{
|
||||
return [
|
||||
'username' => 'user-valid',
|
||||
'pass' => 'SandboxPasswort2023!',
|
||||
// 'account_no'=> '3333333333',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API KEY.
|
||||
*
|
||||
* @since [*next-version*]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_api_secret()
|
||||
{
|
||||
$api_key = defined('PR_DHL_GLOBAL_SECRET') ? PR_DHL_GLOBAL_SECRET : '';
|
||||
|
||||
return $api_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user.
|
||||
*
|
||||
* @return stdClass
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_dhl_my_account()
|
||||
{
|
||||
return $this->api_client->get_user();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,622 @@
|
|||
<?php
|
||||
|
||||
use PR\DHL\REST_API\Interfaces\API_Driver_Interface;
|
||||
use PR\DHL\REST_API\Parcel_DE\Auth;
|
||||
use PR\DHL\REST_API\Parcel_DE\Client;
|
||||
use PR\DHL\REST_API\Parcel_DE\Item_Info;
|
||||
|
||||
// Exit if accessed directly or class already exists
|
||||
if (! defined('ABSPATH') || class_exists('PR_DHL_API_Parcel_DE', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class PR_DHL_API_REST_Parcel_DE extends PR_DHL_API_REST_Paket
|
||||
{
|
||||
/**
|
||||
* The URL to the API.
|
||||
*/
|
||||
const API_URL_PRODUCTION = 'https://api-eu.dhl.com/parcel/de/shipping/';
|
||||
|
||||
/**
|
||||
* The URL to the sandbox API.
|
||||
*/
|
||||
const API_URL_SANDBOX = 'https://api-sandbox.dhl.com/parcel/de/shipping/';
|
||||
|
||||
/**
|
||||
* The API driver instance.
|
||||
*
|
||||
* @var API_Driver_Interface
|
||||
*/
|
||||
public $api_driver;
|
||||
|
||||
/**
|
||||
* The API authorization instance.
|
||||
*
|
||||
* @var Auth
|
||||
*/
|
||||
public $api_auth;
|
||||
|
||||
/**
|
||||
* The API client instance.
|
||||
*
|
||||
* @var Client
|
||||
*/
|
||||
public $api_client;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @throws Exception If an error occurred while creating the API driver, auth or client.
|
||||
*/
|
||||
public function __construct($country_code)
|
||||
{
|
||||
parent::__construct($country_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the API client instance.
|
||||
*
|
||||
* @return Client
|
||||
*
|
||||
* @throws Exception If failed to create the API client.
|
||||
*/
|
||||
protected function create_api_client()
|
||||
{
|
||||
// Create the API client, using this instance's driver and auth objects
|
||||
return new Client(
|
||||
$this->get_api_url(),
|
||||
$this->api_driver,
|
||||
$this->api_auth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get label.
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_dhl_label($args)
|
||||
{
|
||||
$this->set_arguments($args);
|
||||
$uom = get_option('woocommerce_weight_unit');
|
||||
try {
|
||||
$item_info = new Item_Info($args, $uom);
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Create the item and get the barcode
|
||||
$item_response = $this->api_client->create_items([$item_info]);
|
||||
|
||||
if (! empty($item_response['errors'])) {
|
||||
$message = '';
|
||||
foreach ($item_response['errors'] as $error) {
|
||||
$message .= '<br>'.$error['message'];
|
||||
}
|
||||
|
||||
throw new Exception(wp_kses_post($message));
|
||||
}
|
||||
|
||||
if (count($item_response['items']) > 1) {
|
||||
$label_tracking_info = $this->save_multiple_shipments('label', $args['order_details']['order_id'], $item_response['items']);
|
||||
} else {
|
||||
$label_tracking_info = $this->save_export_files('label', $args['order_details']['order_id'], $item_response['items'][0]);
|
||||
}
|
||||
|
||||
return $label_tracking_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete label.
|
||||
*
|
||||
*
|
||||
* @throws Exception.
|
||||
*/
|
||||
public function delete_dhl_label($label_info)
|
||||
{
|
||||
$this->api_client->delete_item($label_info['tracking_number']);
|
||||
|
||||
if (! isset($label_info['label_path'])) {
|
||||
throw new Exception(esc_html__('DHL Label has no path!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$label_path = $label_info['label_path'];
|
||||
|
||||
// Remove the separate return label file too, if one was saved.
|
||||
if (! empty($label_info['return_label_path']) && file_exists($label_info['return_label_path'])) {
|
||||
wp_delete_file($label_info['return_label_path']);
|
||||
}
|
||||
|
||||
if (file_exists($label_path)) {
|
||||
if (! is_writable($label_path)) {
|
||||
throw new Exception(esc_html__('DHL Label file is not writable!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
wp_delete_file($label_path);
|
||||
} else {
|
||||
throw new Exception(esc_html__('DHL Label could not be deleted!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save label file.
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function save_data_file($prefix, $order_id, $label_data)
|
||||
{
|
||||
$data_name = 'dhl-'.$prefix.'-'.$order_id.'.pdf';
|
||||
$data_path = PR_DHL()->get_dhl_label_folder_dir().$data_name;
|
||||
$data_url = PR_DHL()->get_dhl_label_folder_url().$data_name;
|
||||
|
||||
// windows path will not get exception
|
||||
if (validate_file($data_path) > 0 && validate_file($data_path) !== 2) {
|
||||
throw new Exception(esc_html__('Invalid file path!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$label_data_decoded = base64_decode($label_data);
|
||||
$file_ret = file_put_contents($data_path, $label_data_decoded);
|
||||
|
||||
if (empty($file_ret)) {
|
||||
throw new Exception(esc_html__('DHL label file cannot be saved!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
return [
|
||||
'label_url' => $data_url,
|
||||
'label_path' => $data_path,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge export files.
|
||||
*
|
||||
* @throws exception
|
||||
*/
|
||||
public function save_export_files($prefix, $order_id, $item)
|
||||
{
|
||||
|
||||
$file = $this->save_data_file($prefix, $order_id, $item->label->b64);
|
||||
|
||||
// Merge export doc with label
|
||||
if (isset($item->customsDoc->b64)) {
|
||||
$loader = PR_DHL_Libraryloader::instance();
|
||||
$pdfMerger = $loader->get_pdf_merger();
|
||||
|
||||
if ($pdfMerger) {
|
||||
$pdfMerger->addPDF($file['label_path']);
|
||||
|
||||
$file = $this->save_data_file($prefix.'-customs', $order_id, $item->customsDoc->b64);
|
||||
$pdfMerger->addPDF($file['label_path']);
|
||||
|
||||
$filename = 'dhl-'.$prefix.'-'.$order_id.'-export'.'.pdf';
|
||||
$label_url = PR_DHL()->get_dhl_label_folder_url().$filename;
|
||||
$label_path = PR_DHL()->get_dhl_label_folder_dir().$filename;
|
||||
$pdfMerger->merge('file', $label_path);
|
||||
} else {
|
||||
throw new Exception('Failed to merge export files.');
|
||||
}
|
||||
} else {
|
||||
$label_url = $file['label_url'];
|
||||
$label_path = $file['label_path'];
|
||||
}
|
||||
|
||||
// If return label exists, add number
|
||||
$return_num = '';
|
||||
if (isset($item->returnShipmentNo)) {
|
||||
$return_num = $item->returnShipmentNo;
|
||||
}
|
||||
|
||||
// When "combine=false" is sent, DHL returns the return label as its own file. Save it separately.
|
||||
$return_label_url = '';
|
||||
$return_label_path = '';
|
||||
if (isset($item->returnLabel->b64)) {
|
||||
$return_file = $this->save_data_file($prefix.'-return', $order_id, $item->returnLabel->b64);
|
||||
$return_label_url = $return_file['label_url'];
|
||||
$return_label_path = $return_file['label_path'];
|
||||
}
|
||||
|
||||
return [
|
||||
'label_url' => $label_url,
|
||||
'label_path' => $label_path,
|
||||
'tracking_number' => $item->shipmentNo,
|
||||
'return_label_number' => $return_num,
|
||||
'return_label_url' => $return_label_url,
|
||||
'return_label_path' => $return_label_path,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge returned labels.
|
||||
*
|
||||
* @throws exception
|
||||
*/
|
||||
public function save_multiple_shipments($prefix, $order_id, $items)
|
||||
{
|
||||
// Merge PDF files
|
||||
$loader = PR_DHL_Libraryloader::instance();
|
||||
$pdfMerger = $loader->get_pdf_merger();
|
||||
|
||||
$shipmentNos = [];
|
||||
$return_nums = [];
|
||||
$return_label_files = [];
|
||||
|
||||
if ($pdfMerger) {
|
||||
foreach ($items as $key => $item) {
|
||||
$file = $this->save_export_files($prefix.'-'.$key, $order_id, $item);
|
||||
|
||||
$pdfMerger->addPDF($file['label_path']);
|
||||
|
||||
array_push($shipmentNos, $file['tracking_number']);
|
||||
array_push($return_nums, $file['return_label_number']);
|
||||
|
||||
if (! empty($file['return_label_path'])) {
|
||||
$return_label_files[] = $file['return_label_path'];
|
||||
}
|
||||
}
|
||||
|
||||
$filename = 'dhl-'.$prefix.'-'.$order_id.'-multiple'.'.pdf';
|
||||
$label_url = PR_DHL()->get_dhl_label_folder_url().$filename;
|
||||
$label_path = PR_DHL()->get_dhl_label_folder_dir().$filename;
|
||||
$pdfMerger->merge('file', $label_path);
|
||||
} else {
|
||||
throw new Exception('Failed to merge multiple files.');
|
||||
}
|
||||
|
||||
// Merge the separate return labels (combine=false) into a single return PDF.
|
||||
$return_label_url = '';
|
||||
$return_label_path = '';
|
||||
if (! empty($return_label_files)) {
|
||||
$return_merger = $loader->get_pdf_merger();
|
||||
|
||||
if ($return_merger) {
|
||||
foreach ($return_label_files as $return_file) {
|
||||
$return_merger->addPDF($return_file);
|
||||
}
|
||||
|
||||
$return_filename = 'dhl-'.$prefix.'-'.$order_id.'-return-multiple'.'.pdf';
|
||||
$return_label_url = PR_DHL()->get_dhl_label_folder_url().$return_filename;
|
||||
$return_label_path = PR_DHL()->get_dhl_label_folder_dir().$return_filename;
|
||||
$return_merger->merge('file', $return_label_path);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'label_url' => $label_url,
|
||||
'label_path' => $label_path,
|
||||
'tracking_number' => $shipmentNos,
|
||||
'return_label_number' => $return_nums,
|
||||
'return_label_url' => $return_label_url,
|
||||
'return_label_path' => $return_label_path,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request args.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function set_arguments($args)
|
||||
{
|
||||
// Validate set args
|
||||
|
||||
if (empty($args['dhl_settings']['api_user'])) {
|
||||
throw new Exception(esc_html__('Please, provide the username in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['api_pwd'])) {
|
||||
throw new Exception(esc_html__('Please, provide the password for the username in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Validate order details
|
||||
if (empty($args['dhl_settings']['account_num'])) {
|
||||
throw new Exception(esc_html__('Please, provide an account in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['participation'])) {
|
||||
throw new Exception(esc_html__('Please, provide a participation number for the shipping method in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['shipper_name'])) {
|
||||
throw new Exception(esc_html__('Please, provide a shipper name in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['shipper_address'])) {
|
||||
throw new Exception(esc_html__('Please, provide a shipper address in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['shipper_address_no'])) {
|
||||
throw new Exception(esc_html__('Please, provide a shipper address number in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['shipper_address_city'])) {
|
||||
throw new Exception(esc_html__('Please, provide a shipper city in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['shipper_address_zip'])) {
|
||||
throw new Exception(esc_html__('Please, provide a shipper postcode in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if ($args['dhl_settings']['add_logo'] == 'yes' && empty($args['dhl_settings']['shipper_reference'])) {
|
||||
throw new Exception(esc_html__('Please, provide a shipper reference in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Order details
|
||||
if (empty($args['order_details']['dhl_product'])) {
|
||||
throw new Exception(esc_html__('DHL "Product" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// return receiver
|
||||
if (isset($args['order_details']['return_address']) && ($args['order_details']['return_address'] == 'yes')) {
|
||||
|
||||
if (($args['order_details']['dhl_product'] != 'V01PAK') && ($args['order_details']['dhl_product'] != 'V01PRIO') && ($args['order_details']['dhl_product'] != 'V86PARCEL') && ($args['order_details']['dhl_product'] != 'V55PAK')) {
|
||||
throw new Exception(esc_html__('Returns are not supported by this DHL Service.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['return_name'])) {
|
||||
throw new Exception(esc_html__('Please, provide a return name in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['return_address'])) {
|
||||
throw new Exception(esc_html__('Please, provide a return address in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['return_address_no'])) {
|
||||
throw new Exception(esc_html__('Please, provide a return address number in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['return_address_city'])) {
|
||||
throw new Exception(esc_html__('Please, provide a return city in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['dhl_settings']['return_address_zip'])) {
|
||||
throw new Exception(esc_html__('Please, provide a return postcode in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['order_id'])) {
|
||||
throw new Exception(esc_html__('Shop "Order ID" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['weightUom'])) {
|
||||
throw new Exception(esc_html__('Shop "Weight Units of Measure" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (isset($args['order_details']['identcheck']) && ($args['order_details']['identcheck'] == 'yes')) {
|
||||
if (empty($args['shipping_address']['first_name']) || empty($args['shipping_address']['last_name'])) {
|
||||
throw new Exception(esc_html__('First name and last name must be passed for "Identity Check".', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['identcheck_dob']) && empty($args['order_details']['identcheck_age'])) {
|
||||
throw new Exception(esc_html__('Either a "Date of Birth" or "Minimum Age" must be eneted for "Ident-Check".', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate weight
|
||||
try {
|
||||
$this->validate_field('weight', $args['order_details']['weight']);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Weight - '.esc_html($e->getMessage()));
|
||||
}
|
||||
|
||||
if (isset($args['order_details']['multi_packages_enabled']) && ($args['order_details']['multi_packages_enabled'] == 'yes')) {
|
||||
|
||||
if (isset($args['order_details']['total_packages'])) {
|
||||
|
||||
for ($i = 0; $i < intval($args['order_details']['total_packages']); $i++) {
|
||||
|
||||
if (empty($args['order_details']['packages_number'][$i])) {
|
||||
throw new Exception(esc_html__(' A package number is empty. Ensure all package details are filled in.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['packages_weight'][$i])) {
|
||||
throw new Exception(esc_html__('A package weight is empty. Ensure all package details are filled in.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['packages_length'][$i])) {
|
||||
throw new Exception(esc_html__('A package length is empty. Ensure all package details are filled in.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['packages_width'][$i])) {
|
||||
throw new Exception(esc_html__('A package width is empty. Ensure all package details are filled in.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['order_details']['packages_height'][$i])) {
|
||||
throw new Exception(esc_html__('A package height is empty. Ensure all package details are filled in.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (empty($args['order_details']['weight'])) {
|
||||
|
||||
throw new Exception(esc_html__('Order "Weight" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// if ( empty( $args['order_details']['duties'] )) {
|
||||
// throw new Exception( esc_html__('DHL "Duties" is empty!', 'dhl-for-woocommerce') );
|
||||
// }
|
||||
|
||||
if (empty($args['order_details']['currency'])) {
|
||||
throw new Exception(esc_html__('Shop "Currency" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['shipping_address']['city'])) {
|
||||
throw new Exception(esc_html__('Shipping "City" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['shipping_address']['country'])) {
|
||||
throw new Exception(esc_html__('Shipping "Country" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Validate shipping address
|
||||
if (empty($args['shipping_address']['address_1'])) {
|
||||
throw new Exception(esc_html__('Shipping "Address 1" is empty!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$pos_ps = PR_DHL()->is_packstation($args['shipping_address']['address_1']);
|
||||
$pos_rs = PR_DHL()->is_parcelshop($args['shipping_address']['address_1']);
|
||||
$pos_po = PR_DHL()->is_post_office($args['shipping_address']['address_1']);
|
||||
|
||||
// If Packstation, post number is mandatory
|
||||
if ($pos_ps && empty($args['shipping_address']['dhl_postnum'])) {
|
||||
throw new Exception(esc_html__('Post Number is missing, it is mandatory for "Packstation" delivery.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
// Check address 2 if no parcel shop is being selected
|
||||
if (! $pos_ps && ! $pos_rs && ! $pos_po) {
|
||||
// If address 2 missing, set last piece of an address to be address 2
|
||||
if (empty($args['shipping_address']['address_2'])) {
|
||||
$set_key = false;
|
||||
// Break address into pieces by spaces
|
||||
$address_exploded = explode(' ', $args['shipping_address']['address_1']);
|
||||
|
||||
// If no spaces found
|
||||
if (count($address_exploded) == 1) {
|
||||
// Break address into pieces by '.'
|
||||
$address_exploded = explode('.', $args['shipping_address']['address_1']);
|
||||
|
||||
// If no address number and in Germany, return error
|
||||
if (count($address_exploded) === 1 && $args['shipping_address']['country'] === 'DE') {
|
||||
throw new Exception(esc_html__('Shipping street number is missing!', 'dhl-for-woocommerce'));
|
||||
}
|
||||
}
|
||||
|
||||
// If greater than 1, means there are two parts to the address...otherwise Address 2 is empty which is possible in some countries outside of Germany
|
||||
if (count($address_exploded) > 1) {
|
||||
// Loop through address and set number value only...
|
||||
// ...last found number will be 'address_2'
|
||||
foreach ($address_exploded as $address_key => $address_value) {
|
||||
if (is_numeric($address_value)) {
|
||||
// Set last index as street number
|
||||
$set_key = $address_key;
|
||||
}
|
||||
}
|
||||
|
||||
// If no number was found, then take last part of address no matter what it is
|
||||
if ($set_key === false) {
|
||||
$set_key = $address_key;
|
||||
}
|
||||
|
||||
// The number is the first part of address 1
|
||||
if ($set_key === 0) {
|
||||
// Set "address_2" first, as first part
|
||||
$args['shipping_address']['address_2'] = implode(' ', array_slice($address_exploded, 0, 1));
|
||||
// Remove "address_2" from "address_1"
|
||||
$args['shipping_address']['address_1'] = implode(' ', array_slice($address_exploded, 1));
|
||||
} else {
|
||||
// Set "address_2" first
|
||||
$args['shipping_address']['address_2'] = implode(' ', array_slice($address_exploded, $set_key));
|
||||
// Remove "address_2" from "address_1"
|
||||
$args['shipping_address']['address_1'] = implode(' ', array_slice($address_exploded, 0, $set_key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add default values for required fields that might not be passed e.g. phone
|
||||
$default_args = [
|
||||
'shipping_address' => [
|
||||
'name' => '',
|
||||
'company' => '',
|
||||
'address_2' => '',
|
||||
'email' => '',
|
||||
'postcode' => '',
|
||||
'state' => '',
|
||||
'phone' => ' ',
|
||||
],
|
||||
];
|
||||
|
||||
$args['shipping_address'] = wp_parse_args($args['shipping_address'], $default_args['shipping_address']);
|
||||
|
||||
$default_args_item = [
|
||||
'item_description' => '',
|
||||
'sku' => '',
|
||||
'line_total' => 0,
|
||||
'country_origin' => '',
|
||||
'hs_code' => '',
|
||||
'qty' => 1,
|
||||
];
|
||||
|
||||
foreach ($args['items'] as $key => $item) {
|
||||
|
||||
if (! empty($item['hs_code'])) {
|
||||
try {
|
||||
$this->validate_field('hs_code', $item['hs_code']);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('HS Code - '.esc_html($e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$args['items'][$key] = wp_parse_args($item, $default_args_item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate field.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function validate_field($key, $value)
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
switch ($key) {
|
||||
case 'weight':
|
||||
$this->validate(wc_format_decimal($value));
|
||||
break;
|
||||
case 'hs_code':
|
||||
$this->validate($value, 'string', 4, 11);
|
||||
break;
|
||||
case 'pickup':
|
||||
$this->validate($value, 'string', 14, 14);
|
||||
break;
|
||||
case 'distribution':
|
||||
$this->validate($value, 'string', 6, 6);
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate value.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function validate($value, $type = 'int', $min_len = 0, $max_len = 0)
|
||||
{
|
||||
|
||||
switch ($type) {
|
||||
case 'string':
|
||||
if ((strlen($value) < $min_len) || (strlen($value) > $max_len)) {
|
||||
if ($min_len == $max_len) {
|
||||
/* translators: %s is the required number of characters */
|
||||
throw new Exception(sprintf(esc_html__('The value must be %s characters.', 'dhl-for-woocommerce'), esc_attr($min_len)));
|
||||
} else {
|
||||
/* translators: %1$s is the minimum number of characters, %2$s is the maximum number of characters */
|
||||
throw new Exception(sprintf(esc_html__('The value must be between %1$s and %2$s characters.', 'dhl-for-woocommerce'), esc_attr($min_len), esc_attr($max_len)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
if (! is_numeric($value)) {
|
||||
throw new Exception(esc_html__('The value must be a number', 'dhl-for-woocommerce'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit;
|
||||
} // Exit if accessed directly
|
||||
|
||||
class PR_DHL_API_REST_Parcel extends PR_DHL_API_REST
|
||||
{
|
||||
// const PR_DHL_AUTO_CLOSE = '1';
|
||||
|
||||
private $args = [];
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function get_dhl_parcel_services($args)
|
||||
{
|
||||
// curl -X GET --header 'Accept: application/json' --header 'X-EKP: 2222222222' 'https://cig.dhl.de/services/sandbox/rest/checkout/28757/availableServices?startDate=2018-08-17'
|
||||
|
||||
$this->set_arguments($args);
|
||||
$this->set_endpoint('/checkout/'.$args['postcode'].'/availableServices');
|
||||
$this->set_query_string();
|
||||
|
||||
return $this->get_request();
|
||||
}
|
||||
|
||||
protected function set_arguments($args)
|
||||
{
|
||||
// Validate set args
|
||||
|
||||
if (empty($args['account_num'])) {
|
||||
throw new Exception(esc_html__('Please, provide an account in the DHL shipping settings', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['postcode'])) {
|
||||
throw new Exception(esc_html__('Please, provide the receiver postnumber.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
if (empty($args['start_date'])) {
|
||||
throw new Exception(esc_html__('Please, provide the shipment start date.', 'dhl-for-woocommerce'));
|
||||
}
|
||||
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
protected function set_query_string()
|
||||
{
|
||||
// 2018-08-17
|
||||
$dhl_label_query_string = ['startDate' => $this->args['start_date']];
|
||||
|
||||
$this->query_string = http_build_query($dhl_label_query_string);
|
||||
}
|
||||
|
||||
protected function get_query_string()
|
||||
{
|
||||
return $this->query_string;
|
||||
}
|
||||
|
||||
protected function set_header($authorization = '')
|
||||
{
|
||||
$dhl_header['Accept'] = 'application/json';
|
||||
$dhl_header['X-EKP'] = $this->args['account_num'];
|
||||
|
||||
if (! empty($authorization)) {
|
||||
$dhl_header['Authorization'] = $authorization;
|
||||
}
|
||||
|
||||
$this->remote_header = $dhl_header;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
interface PR_DHL_API_Label
|
||||
{
|
||||
public function get_dhl_label($args);
|
||||
|
||||
public function delete_dhl_label($args);
|
||||
|
||||
public function dhl_test_connection($client_id, $client_secret);
|
||||
|
||||
public function dhl_validate_field($key, $value);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue