Partnercenter-API: WooCommerce-Plugin (v1.3.3) als Referenz eingecheckt

Der in den Partner-Shops installierte WooCommerce-Connector, der Bestellungen
per REST an /api/wp/* uebertraegt (Kunde anlegen, Warenkorb, Status-Abgleich).
Grundlage fuer AP-13 (Shop-Bestandsabzug beim Versand).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-15 15:46:16 +00:00
parent a14751ed10
commit 592b03ace5
13 changed files with 2023 additions and 0 deletions

View file

@ -0,0 +1,200 @@
<?php
if (! defined('ABSPATH')) {
exit;
}
/**
* Class PARTNERCENTER_API_Order_Forwarding_Button_MetaBox
*/
class PARTNERCENTER_API_Order_Forwarding_Button_MetaBox
{
/**
* PARTNERCENTER_API_Order_Forwarding_Button_MetaBox constructor.
*/
public function __construct()
{
if (is_admin()) {
add_action('load-post.php', [$this, 'init_metabox']);
add_action('load-post-new.php', [$this, 'init_metabox']);
}
}
/**
* Add methods to hooks.
*
* @since 1.0.0
*/
public function init_metabox()
{
add_action('add_meta_boxes', [$this, 'add_metabox']);
add_action('save_post', [$this, 'save_metabox'], 10, 2);
}
/**
* Register the meta box.
*
* @since 1.0.0
*/
public function add_metabox()
{
add_meta_box(
'partnercenter-order-forwarding-button',
__('Fulfillment by PARTNERCENTER', 'partnercenter-api'),
[$this, 'render_metabox'],
'shop_order',
'side',
'default'
);
}
/**
* Callback to render the meta box content.
*
*
* @since 1.0.0
*/
public function render_metabox($post)
{
// Add nonce for security and authentication.
wp_nonce_field('partnercenter_api_order_forwarding_nonce_action', 'partnercenter_api_order_forwarding_nonce');
// Retrieve an existing value from the database.
$order_send_status = intval(get_post_meta($post->ID, '_partnercenter_api_order_fulfillment_forwarded', true));
if ($order_send_status !== 0) { ?>
<p>
<?php
printf(__('Order send at %s', 'partnercenter-api'), date(get_option('date_format'), $order_send_status));
?>
</p>
<p>
<?php
// Get status into var
$order_send_status = get_post_meta($post->ID, '_partnercenter_api_order_status', true);
// Check status
switch ($order_send_status) {
case 'free':
_e('Self-shipping.', 'partnercenter-api');
break;
case 'open':
case 'process':
_e('Order waiting.', 'partnercenter-api');
break;
case 'sent':
case 'close':
_e('Order sent.', 'partnercenter-api');
break;
default:
_e('Unclear status.', 'partnercenter-api');
break;
}
?>
</p>
<?php
// Check if order is open
if ($order_send_status === 'open') {
// Show button to allow canceling order
?>
<p>
<button id="order_fulfillment_internal_retrieve" name="order_fulfillment_internal_retrieve"
class="button button-primary" value="yes">
<?php esc_html_e('Retrieve', 'aloe-vera-shipment-interface'); ?>
</button>
</p>
<?php } ?>
<?php } elseif (empty(get_post_meta($post->ID, '_partnercenter_api_order_canceled', true))) { ?>
<p>
<button id="partnercenter_api_order_fulfillment_forwarded" name="partnercenter_api_order_fulfillment_forwarded"
class="button button-primary" value="yes">
<?php _e('Send to PARTNERCENTER for fulfillment', 'partnercenter-api'); ?>
</button>
</p>
<?php } else { ?>
<p>
<?php
printf(__('Order canceled at PARTNERCENTER on %s', 'partnercenter-api'), date(get_option('date_format'), (int) get_post_meta($post->ID, '_partnercenter_api_order_canceled', true)));
?>
</p>
<?php }
}
/**
* Callback to handle metabox submission.
*
*
* @since 1.0.0
*/
public function save_metabox($post_id, $post)
{
// Add nonce for security and authentication.
$nonce_name = isset($_POST['partnercenter_api_order_forwarding_nonce']) ? $_POST['partnercenter_api_order_forwarding_nonce'] : '';
$nonce_action = 'partnercenter_api_order_forwarding_nonce_action';
// Check if a nonce is set.
if (! isset($nonce_name)) {
return;
}
// Check if a nonce is valid.
if (! wp_verify_nonce($nonce_name, $nonce_action)) {
return;
}
// Check if the user has permissions to save data.
if (! current_user_can('edit_shop_orders', $post_id)) {
return;
}
// Check if it's not an autosave.
if (wp_is_post_autosave($post_id)) {
return;
}
// Check if it's not a revision.
if (wp_is_post_revision($post_id)) {
return;
}
// Sanitize and check if user wants to forward item
if (isset($_POST['partnercenter_api_order_fulfillment_forwarded']) && sanitize_title($_POST['partnercenter_api_order_fulfillment_forwarded']) === 'yes') {
/**
* Call action to allow plugins to react.
*
* @param int $order_id
*/
do_action('partnercenter_api_send_order_items', $post_id);
}
// Sanitize and check if user wants to retrieve item
if (isset($_POST['order_fulfillment_internal_retrieve']) && sanitize_title($_POST['order_fulfillment_internal_retrieve']) === 'yes') {
/**
* Call action to allow plugins to react.
*
* @param int $order_id
*/
do_action('partnercenter_api_retrieve_order', $post_id);
}
}
}
new PARTNERCENTER_API_Order_Forwarding_Button_MetaBox;

View file

@ -0,0 +1,135 @@
<?php
/*
* Add to Post List Class
*/
if (! defined('ABSPATH')) {
exit;
}
class PARTNERCENTER_API_Order_Post_List_Class
{
/**
* PARTNERCENTER_API_Order_Post_List_Class constructor.
*/
public function __construct()
{
// Add custom column
add_filter('manage_edit-shop_order_columns', [$this, 'manage_edit_shop_order_columns'], 20);
// Fill the column
add_action('manage_shop_order_posts_custom_column', [
$this,
'manage_shop_order_posts_custom_column',
], 20, 2);
// Style custom column
add_action('admin_print_styles', [$this, 'admin_print_styles'], 20);
}
/**
* Add custom column.
*
*
* @return array
*
* @since 1.0.0
*/
public function manage_edit_shop_order_columns(array $columns)
{
$new_columns = [];
foreach ($columns as $column_name => $column_info) {
$new_columns[$column_name] = $column_info;
if ($column_name === 'order_status') {
$new_columns['partnercenter-status'] = __('PARTNERCENTER Status', 'partnercenter-api');
}
}
return $new_columns;
}
/**
* Add content to custom column.
*
* @param int $order_id
*
* @since 1.0.0
*/
public function manage_shop_order_posts_custom_column(string $column, $order_id)
{
// Check if our column is worked on
if ($column !== 'partnercenter-status') {
return;
}
// Check if order was send to PARTNERCENTER
$forwarded = intval(get_post_meta($order_id, '_partnercenter_api_order_fulfillment_forwarded', true));
if ($forwarded === 0) {
return;
}
// Display the status from DB?>
<?php
switch (get_post_meta($order_id, '_partnercenter_api_order_status', true)) {
case 'free':
break;
case 'sent':
case 'close': ?>
<div class="partnercenter-status-circle green"></div>
<?php
break;
case 'open':
case 'process': ?>
<div class="partnercenter-status-circle orange"></div>
<?php
break;
default: ?>
<span style="font-size: 200%">
<?php _e('⚠️', 'partnercenter-api'); ?>
</span>
<?php break;
} ?>
<?php }
/**
* Style custom column.
*/
public function admin_print_styles()
{
// Get the screen
if (get_current_screen()->id !== 'edit-shop_order') {
return;
} ?>
<style type="text/css">
th.column-partnercenter-status {
text-align: center;
}
.partnercenter-status-circle {
width: 25px;
height: 25px;
border-radius: 100%;
background: darkorange;
margin: 0 auto;
}
.partnercenter-status-circle.green {
background: limegreen;
}
</style>
<?php }
}
new PARTNERCENTER_API_Order_Post_List_Class;

View file

@ -0,0 +1,108 @@
msgid ""
msgstr ""
"Project-Id-Version: PARTNERCENTER API Connector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-07 17:26+0000\n"
"PO-Revision-Date: 2021-01-07 17:28+0000\n"
"Last-Translator: Ralf\n"
"Language-Team: Deutsch\n"
"Language: de_DE\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Loco https://localise.biz/\n"
"X-Loco-Version: 2.4.4; wp-5.4.2"
#: admin/order-forwarding-button-metabox.php:44
msgid "Fulfillment by PARTNERCENTER"
msgstr "Versand durch PARTNERCENTER"
#: admin/order-forwarding-button-metabox.php:72
#, php-format
msgid "Order send at %s"
msgstr "Bestellung übermittelt am %s"
#: admin/order-forwarding-button-metabox.php:79
msgid "Self-shipping."
msgstr "Eigenversand."
#: admin/order-forwarding-button-metabox.php:82
msgid "Order waiting."
msgstr "Bestellung wartet auf Versand."
#: admin/order-forwarding-button-metabox.php:85
msgid "Order sent."
msgstr "Bestellung versandt."
#: admin/order-forwarding-button-metabox.php:88
msgid "Unclear status."
msgstr "Undeutlicher Status."
#: admin/order-forwarding-button-metabox.php:98
msgid "Send to PARTNERCENTER for fulfillment"
msgstr "Übergabe PARTNERCENTER Versand"
#: admin/post-list.php:49
msgid "PARTNERCENTER Status"
msgstr "PARTNERCENTER-Status"
#: admin/post-list.php:93
msgid "⚠️"
msgstr "⚠️"
#: public/end-point.php:71
msgid "Missing order ID"
msgstr "Fehlende Bestellungsnummer"
#: public/end-point.php:74
msgid "Missing document ID"
msgstr "Fehlende Dokumentennummer"
#: public/end-point.php:77
msgid "Missing Request token"
msgstr "Fehlender Token"
#: public/end-point.php:83
msgid "Invalid order"
msgstr "Ungültig Bestellung"
#: public/end-point.php:91
msgid "Invalid token"
msgstr "Ungültiger Token"
#: public/end-point.php:99
msgid "Mismatching invoice number"
msgstr "Ungleiche Rechungsnummern"
#: public/end-point.php:104
msgid "File not found"
msgstr "Datei nicht gefunden"
#: public/partnercenter-api/partnercenter-api-requests/auth.php:101
msgid "Login fails or is missing"
msgstr "Logindaten fehlen oder schlagen fehl"
#: public/partnercenter-api/partnercenter-api-requests/auth.php:106
msgid "Login fails or is to old"
msgstr "Login ist falsch oder zu alt"
#: public/partnercenter-api/partnercenter-api-requests/auth.php:111
msgid "Auth token is missing"
msgstr "Authentifizierungstoken fehlt"
#. Name of the plugin
msgid "PARTNERCENTER API Connector"
msgstr "PARTNERCENTER API Connector"
#. Description of the plugin
msgid "Connect WooCommerce to PARTNERCENTER API"
msgstr "Verbindet WooCommerce mit PARTNERCENTER API"
#. Author of the plugin
msgid "Cross Media Cloud"
msgstr "Cross Media Cloud"
#. Author URI of the plugin
msgid "https://www.cross-media-cloud.de"
msgstr "https://www.cross-media-cloud.de"

View file

@ -0,0 +1,109 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PARTNERCENTER API Connector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-07 17:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: \n"
"Language: \n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Loco https://localise.biz/\n"
"X-Loco-Version: 2.4.0; wp-5.4.2"
#: admin/order-forwarding-button-metabox.php:44
msgid "Fulfillment by PARTNERCENTER"
msgstr ""
#: admin/order-forwarding-button-metabox.php:72
#, php-format
msgid "Order send at %s"
msgstr ""
#: admin/order-forwarding-button-metabox.php:79
msgid "Self-shipping."
msgstr ""
#: admin/order-forwarding-button-metabox.php:82
msgid "Order waiting."
msgstr ""
#: admin/order-forwarding-button-metabox.php:85
msgid "Order sent."
msgstr ""
#: admin/order-forwarding-button-metabox.php:88
msgid "Unclear status."
msgstr ""
#: admin/order-forwarding-button-metabox.php:98
msgid "Send to PARTNERCENTER for fulfillment"
msgstr ""
#: admin/post-list.php:49
msgid "PARTNERCENTER Status"
msgstr ""
#: admin/post-list.php:93
msgid "⚠️"
msgstr ""
#: public/end-point.php:71
msgid "Missing order ID"
msgstr ""
#: public/end-point.php:74
msgid "Missing document ID"
msgstr ""
#: public/end-point.php:77
msgid "Missing Request token"
msgstr ""
#: public/end-point.php:83
msgid "Invalid order"
msgstr ""
#: public/end-point.php:91
msgid "Invalid token"
msgstr ""
#: public/end-point.php:99
msgid "Mismatching invoice number"
msgstr ""
#: public/end-point.php:104
msgid "File not found"
msgstr ""
#: public/partnercenter-api/partnercenter-api-requests/auth.php:101
msgid "Login fails or is missing"
msgstr ""
#: public/partnercenter-api/partnercenter-api-requests/auth.php:106
msgid "Login fails or is to old"
msgstr ""
#: public/partnercenter-api/partnercenter-api-requests/auth.php:111
msgid "Auth token is missing"
msgstr ""
#. Name of the plugin
msgid "PARTNERCENTER API Connector"
msgstr ""
#. Description of the plugin
msgid "Connect WooCommerce to PARTNERCENTER API"
msgstr ""
#. Author of the plugin
msgid "Cross Media Cloud"
msgstr ""
#. Author URI of the plugin
msgid "https://www.cross-media-cloud.de"
msgstr ""

View file

@ -0,0 +1,103 @@
<?php
/**
* Plugin Name: PARTNERCENTER API Connector
* Description: Connect WooCommerce to PARTNERCENTER API
* Version: 1.3.3
* Author: Cross Media Cloud
* Author URI: https://www.cross-media-cloud.de
* License: GNU General Public License v2 or later
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: partnercenter-api
* Domain Path: /languages/
*/
if (! defined('ABSPATH')) {
exit;
}
/**
* Load plugin text domain
*
* @since 1.0.0
*/
function partnercenter_api_load_textdomain()
{
load_plugin_textdomain('partnercenter-api', false, plugin_basename(dirname(__FILE__)).'/languages');
}
add_action('plugins_loaded', 'partnercenter_api_load_textdomain');
/**
* Function to run after plugin activation
*
* @since 1.0.0
*/
function partnercenter_api_activate()
{
// Add event to wp cron
wp_schedule_event(time(), 'hourly', 'partnercenter_api_fetch_orders_status');
}
register_activation_hook(__FILE__, 'partnercenter_api_activate');
/**
* Function to run after plugin deactivation
*
* @since 1.0.0
*/
function partnercenter_api_deactivate()
{
/*
* Remove order status check from wp cron
*/
$timestamp = wp_next_scheduled('partnercenter_api_fetch_orders_status');
wp_unschedule_event($timestamp, 'partnercenter_api_fetch_orders_status');
// Clean hook
wp_clear_scheduled_hook('partnercenter_api_fetch_orders_status');
}
register_deactivation_hook(__FILE__, 'partnercenter_api_deactivate');
/**
* Load API class if needed
*
* @since 1.0.0
*/
function partnercenter_api_init()
{
/**
* Load the API class.
*/
require_once plugin_dir_path(__FILE__).'public/partnercenter-api/partnercenter-api.php';
return new PARTNERCENTER_API_Requests;
}
add_filter('PARTNERCENTER_API', 'partnercenter_api_init');
/*
* Load all public features.
*/
// Load testing short code
require_once 'public/shortcode.php';
// Load WooCommerce status related features
require_once 'public/woocommerce/order-status.php';
// Load endpoint
require_once 'public/end-point.php';
/*
* Load admin features.
*/
if (is_admin()) {
// Load post list features
require_once 'admin/post-list.php';
// Load button to forward order to fulfilment by PARTNERCENTER
require_once 'admin/order-forwarding-button-metabox.php';
}

View file

@ -0,0 +1,156 @@
<?php
// Exit if accessed directly
if (! defined('ABSPATH')) {
exit;
}
/*
* Class to add a custom endpoint for vendor decisions
*
* @since 1.1.0
*/
class PARTNERCENTER_API_Custom_Document_Download_Endpoint_Class
{
/**
* Custom endpoint name.
*
* @since 1.1.0
*
* @var string
*/
public static $endpoint = 'partnercenter-document-download';
/*
* Setup the class.
*/
public function __construct()
{
add_action('init', [$this, 'rewrite']);
add_filter('query_vars', [$this, 'query_vars']);
add_filter('template_redirect', [$this, 'change_template'], 1, 0);
}
/*
* Allow custom url parameter.
*
* @since 1.1.0
*/
public function rewrite()
{
// Add some custom query vars
add_rewrite_tag('%order-id%', '([^&]+)');
add_rewrite_tag('%document-id%', '([^&]+)');
add_rewrite_tag('%request-token%', '([^&]+)');
// Add rewrite rule to map own query vars
add_rewrite_rule('^'.self::$endpoint.'/([^/]*)/([^/]*)/([^/]*)/?', 'index.php?'.self::$endpoint.'=1&order-id=$matches[1]&document-id=$matches[2]&request-token=$matches[3]', 'top');
}
/*
* Allow custom query var.
*
* @since 1.1.0
*/
public function query_vars($vars)
{
$vars[] = self::$endpoint;
return $vars;
}
/**
* Fetch and display the template for this virtual page.
*
* @since 1.1.0
*/
public function show_response(array $values)
{
/*
* Check for empty values
*/
if (empty($values['order-id'])) {
wp_die(__('Missing order ID', 'partnercenter-api'));
}
if (empty($values['document-id'])) {
wp_die(__('Missing document ID', 'partnercenter-api'));
}
if (empty($values['request-token'])) {
wp_die(__('Missing Request token', 'partnercenter-api'));
}
// Fetch order from DB
$order = wc_get_order($values['order-id']);
if ($order === false) {
wp_die(__('Invalid order', 'partnercenter-api'));
}
/*
* Validate token
*/
$token_from_db = get_post_meta($order->get_id(), '_partnercenter_invoice_token', true);
if ($token_from_db !== $values['request-token']) {
wp_die(__('Invalid token', 'partnercenter-api'));
}
// Get invoice from DB
$invoice_from_db = wc_gzdp_get_order_last_invoice($order->get_id());
// Check if the right invoice id was requested
if (intval($invoice_from_db->get_id()) !== intval($values['document-id'])) {
wp_die(__('Mismatching invoice number', 'partnercenter-api'));
}
// Check if file exists
if (! file_exists($invoice_from_db->get_pdf_path())) {
wp_die(__('File not found', 'partnercenter-api'));
}
// Send headers to download file
header($_SERVER['SERVER_PROTOCOL'].' 200 OK');
header('Cache-Control: public'); // needed for internet explorer
header('Content-Type: application/pdf');
header('Content-Transfer-Encoding: Binary');
header('Content-Length:'.filesize($invoice_from_db->get_pdf_path()));
header('Content-Disposition: attachment; filename= '.$invoice_from_db->get_filename());
readfile($invoice_from_db->get_pdf_path());
// Stop the rest of the process
exit();
}
/*
* Load right template for custom virtual page.
*
* @since 1.1.0
*/
public function change_template()
{
/** @var $wp_query WP_Query */
global $wp_query;
if (! empty($wp_query->query_vars[self::$endpoint])) {
// Set some data
$wp_query->is_404 = false;
$wp_query->is_feed = false;
$wp_query->is_page = true;
$wp_query->is_singular = true;
$wp_query->is_home = false;
$wp_query->is_archive = false;
$wp_query->is_category = false;
// Collect values to pass on
$values = [
'order-id' => sanitize_title($wp_query->query_vars['order-id']),
'document-id' => sanitize_title($wp_query->query_vars['document-id']),
'request-token' => sanitize_title($wp_query->query_vars['request-token']),
];
self::show_response($values);
}
}
}
new PARTNERCENTER_API_Custom_Document_Download_Endpoint_Class;

View file

@ -0,0 +1,137 @@
<?php
/*
* Hold all auth related request.
*/
trait PARTNERCENTER_API_Auth
{
/**
* @var string Store fetched access token
*/
private $api_access_token;
/**
* @var int Timestamp until the access token is valid.
*/
private $api_login_lifetime;
/**
* Method to log in to API.
*
* @return bool
*/
private function login()
{
// Build URL
$url = $this->build_url('auth/login');
// Build body details
$request_body = [
'email' => PARTNERCENTER_API_EMAIL,
'password' => PARTNERCENTER_API_PASSWORD,
'remember_me' => true,
];
// Return the POST request responds
$login_response = wp_safe_remote_post($url, [
'blocking' => true,
'headers' => $this->get_headers(),
'body' => json_encode($request_body),
]
);
// Check if login went well
if (wp_remote_retrieve_response_code($login_response) !== 200) {
// Reset all login details on error
$this->reset_login();
return false;
}
// Get data to work with
$response_body = (object) json_decode(wp_remote_retrieve_body($login_response));
// Set access to for all requests
$this->api_access_token = $response_body->access_token;
// Set life time data
$this->api_login_lifetime = strtotime($response_body->expires_at);
return true;
}
/**
* Method to log out.
*/
private function logout()
{
// Build URL
$url = $this->build_url('auth/logout');
// Make sure to be logged in
$this->authenticate();
// Send POST request
$logout_response = wp_safe_remote_post($url, [
'blocking' => true,
'headers' => $this->get_headers(),
]
);
// Check if login went well
if (wp_remote_retrieve_response_code($logout_response) !== 200) {
return false;
}
// Reset login details
$this->reset_login();
return true;
}
/**
* Helper to setup authentication with API.
*
* @return bool|WP_Error
*/
private function authenticate()
{
// Check if lifetime is set and can't be restored
if (empty($this->api_login_lifetime) && ! $this->login()) {
return new WP_Error('auth_failed', __('Login fails or is missing', 'partnercenter-api'));
}
// Check if access token is still valid and can't be restored
if ($this->api_login_lifetime < time() && ! $this->login()) {
return new WP_Error('auth_failed', __('Login fails or is to old', 'partnercenter-api'));
}
// Check if access token is set
if (empty($this->api_access_token)) {
return new WP_Error('auth_failed', __('Auth token is missing', 'partnercenter-api'));
}
// Reset all header fields
$this->reset_request();
// Add access token to header
$this->headers['Authorization'] = 'Bearer '.$this->api_access_token;
return true;
}
/**
* Helper to reset login.
*/
private function reset_login()
{
unset($this->api_access_token);
unset($this->api_login_lifetime);
}
}

View file

@ -0,0 +1,122 @@
<?php
/*
* Hold all http request methods.
*/
trait PARTNERCENTER_API_Methods
{
/**
* Reset all things to be able to send requests.
*/
private function reset_request()
{
$this->headers = [
'Content-Type' => 'application/json',
'X-Requested-With' => 'XMLHttpRequest',
];
}
/**
* Build the URL.
*
* @param array $args
* @return string
*/
private function build_url(string $endpoint, $args = [])
{
return add_query_arg($args, trailingslashit($this->api_url).$endpoint);
}
/**
* Send a POST request.
*
*
* @return array|WP_Error
*/
private function create(string $endpoint, array $body = [])
{
// Make sure to be logged in
$this->authenticate();
// Build URL
$url = $this->build_url($endpoint);
// Return the POST request responds
return wp_safe_remote_post($url, [
'blocking' => true,
'headers' => $this->get_headers(),
'body' => json_encode($body),
]
);
}
/**
* Send a GET request.
*
*
* @return array|WP_Error
*/
private function read(string $endpoint)
{
// Make sure to be logged in
$this->authenticate();
// Build URL
$url = $this->build_url($endpoint);
// Return the GET request responds
return wp_safe_remote_get($url, [
'blocking' => true,
'headers' => $this->get_headers(),
]
);
}
/**
* Send PATCH request.
*
*
* @return array|WP_Error
*/
private function update(string $endpoint, array $body = [])
{
// Make sure to be logged in
$this->authenticate();
// Build URL
$url = $this->build_url($endpoint);
$request_args = [
'method' => 'PATCH',
'headers' => $this->get_headers(),
'body' => json_encode($body),
];
return wp_remote_request($url, $request_args);
}
/**
* Helper to check CRUD requests responses.
*
*
* @return bool
*/
private function handle_response($response)
{
// Check if an error returned
if (wp_remote_retrieve_response_code($response) !== 200) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,424 @@
<?php
/*
* Hold all order related request.
*/
trait PARTNERCENTER_API_Order
{
/**
* Store order object.
*
* @since 1.0.0
*
* @var WC_Order Store WooCommerce order data
*/
private $order;
/**
* Store order ID.
*
* @since 1.0.0
*
* @var int Order ID
*/
private $order_id;
/**
* Helper to setup order object to work with.
*
*
* @since 1.0.0
*/
private function setup_order(int $order_id)
{
$this->order_id = absint($order_id);
// Fetch order object and make it accessibly for all methods
$this->order = new WC_Order($order_id);
}
/**
* Get the invoice url.
*
* @return string|bool
*/
private function invoice_data()
{
// Check if function exists
if (! function_exists('wc_gzdp_get_order_last_invoice')) {
return false;
}
// Get the latest invoice
$invoice = wc_gzdp_get_order_last_invoice($this->order);
// Check received data
if (empty($invoice) || gettype($invoice) !== 'object') {
return false;
}
// Try to get attachment ID
$attachment_id = get_post_meta($invoice->id, '_invoice_attachment', true);
// Check if a value was found
if (empty($attachment_id)) {
return false;
}
// Try to get file path
$file_relative_path = get_post_meta($attachment_id, '_wp_attached_file', true);
if (empty($GLOBALS['woocommerce_germanized_pro'])) {
return false;
}
// Get get detail vom Class
$upload_folder = $GLOBALS['woocommerce_germanized_pro']->get_upload_dir();
// Build and return the final url to the invoice file
return $upload_folder['baseurl'].'/'.$file_relative_path;
}
/**
* Helper to setup address data.
*
* @return array
*
* @since 1.0.0
*/
private function compose_address_body()
{
// Set up body args
$data = [
'billing_salutation' => intval($this->order->get_meta('_billing_title', true)),
'billing_company' => $this->order->get_billing_company(),
'billing_firstname' => $this->order->get_billing_first_name(),
'billing_lastname' => $this->order->get_billing_last_name(),
'billing_address' => $this->order->get_billing_address_1(),
'billing_address_2' => $this->order->get_billing_address_2(),
'billing_zipcode' => $this->order->get_billing_postcode(),
'billing_city' => $this->order->get_billing_city(),
'billing_country_code' => $this->order->get_billing_country(),
'billing_phone' => $this->order->get_billing_phone(),
'billing_email' => strtolower($this->order->get_billing_email()),
];
// Check if a shipping and billing address are the same
if ($this->order->get_formatted_billing_address() === $this->order->get_formatted_shipping_address()) {
// Set value to skip shipping address
$data['same_as_billing'] = true;
} else {
// Extent array with more details
$data = array_merge($data, [
'same_as_billing' => false,
'shipping_salutation' => intval($this->order->get_meta('_shipping_title', true)),
'shipping_company' => $this->order->get_shipping_company(),
'shipping_firstname' => $this->order->get_shipping_first_name(),
'shipping_lastname' => $this->order->get_shipping_last_name(),
'shipping_address' => $this->order->get_shipping_address_1(),
'shipping_address_2' => $this->order->get_shipping_address_2(),
'shipping_zipcode' => $this->order->get_shipping_postcode(),
'shipping_city' => $this->order->get_shipping_city(),
'shipping_country_code' => $this->order->get_shipping_country(),
'shipping_phone' => $this->order->get_billing_phone(),
]);
}
// Try to set order invoice
$invoice_url = $this->invoice_data($this->order->get_id());
if ($invoice_url !== false) {
$data['wp_invoice_path'] = $invoice_url;
}
return $data;
}
/**
* Helper to create cart data.
*
* @return array
*
* @since 1.0.0
*/
private function compose_cart_content()
{
// Start of empty cart
$cart = [];
// Loop all order items
foreach ($this->order->get_items() as $order_item) {
// Get product ID of order item
$product_id = $order_item->get_product_id();
// Get product object
$product = wc_get_product($product_id);
// Get sum of total and taxes
$line_total = $order_item->get_total() + $order_item->get_total_tax();
// Divide line total by quantity
$item_price = $line_total / $order_item->get_quantity();
// Round item price to usual format
$item_price_rounded = number_format($item_price, wc_get_price_decimals());
// Convert price to the way the API likes it
$item_price_converted = intval($item_price_rounded * pow(10, wc_get_price_decimals()));
// Build array item
$cart[] = [
'article' => $product->get_sku(),
'qty' => intval($order_item->get_quantity()),
'price' => $item_price_converted,
];
}
return $cart;
}
/**
* Create order in API.
*
* @since 1.0.0
*/
private function write_order()
{
// Start body by setting all address data
$request_body = $this->compose_address_body();
// Add dev mode
if (PARTNERCENTER_API_DEV) {
$request_body['mode'] = 'dev';
}
// Set some order details
$request_body['wp_order_number'] = $this->order->get_id();
$request_body['wp_order_date'] = $this->order->get_date_created()->date('Y-m-d H:i:s');
// Send to API
$response = $this->create('wp/store', $request_body);
// Handle response
return $this->handle_response($response);
}
/**
* Add address details into Order in API.
*
* @since 1.0.0
*/
private function write_order_changes()
{
// Start body by setting all address data
$request_body = $this->compose_address_body();
// Add dev mode
if (PARTNERCENTER_API_DEV) {
$request_body['mode'] = 'dev';
}
// Set some order details
$request_body['wp_order_number'] = $this->order->get_id();
// Send to API
$response = $this->create('wp/update', $request_body);
// Handle response
return $this->handle_response($response);
}
/**
* Add cart items to order in API.
*
* @since 1.0.0
*/
private function write_order_items()
{
/*
* Order details
*/
// Get all cart details
$cart = $this->compose_cart_content();
// Build up all body data
$request_body = [
'wp_order_number' => $this->order->get_id(),
'wp_order' => $cart,
];
/*
* Add invoice
*/
// Try to get order invoice
$invoice = wc_gzdp_get_order_last_invoice($this->order->get_id());
if (! is_null($invoice)) {
// generate random token
$download_token = bin2hex(random_bytes(20));
$download_token = substr($download_token, 0, 10);
// Save token to post meta
update_post_meta($this->order->get_id(), '_partnercenter_invoice_token', $download_token);
// Build the invoice URL
$invoice_path = sprintf('/partnercenter-document-download/%s/%s/%s/', $this->order->get_id(), $invoice->get_id(), $download_token);
$invoice_url = home_url($invoice_path);
if ($invoice_url !== false) {
$request_body['wp_invoice_path'] = $invoice_url;
}
}
/*
* Add advertising
*/
$flyer_ids_string = get_post_meta($this->order->get_id(), 'timopaul_woocommerce_customers_statistics_werbemittel', true);
if (! empty($flyer_ids_string)) {
// Break IDs into array
$flyer_ids_array = explode(',', $flyer_ids_string);
// Prepare empty array
$flyer_strings_array = [];
// Loop the flyers
foreach ($flyer_ids_array as $id) {
// Add all flyer titles to array
$flyer_strings_array[] = get_the_title($id);
}
// Add contacted string to request
$request_body['wp_advertising'] = implode(', ', $flyer_strings_array);
}
/*
* Add incentives
*/
$incentives_ids_string = get_post_meta(get_the_ID(), 'timopaul_woocommerce_customers_statistics_incentives', true);
if (! empty($incentives_ids_string)) {
// Break IDs into array
$incentives_ids_array = explode(',', $incentives_ids_string);
// Prepare empty array
$incentives_strings_array = [];
// Loop the flyers
foreach ($incentives_ids_array as $id) {
// Add all flyer titles to array
$incentives_strings_array[] = get_the_title($id);
}
// Add contacted string to request
$request_body['wp_incentives'] = implode(', ', $incentives_strings_array);
}
/*
* Make the request
*/
// Send to API
$response = $this->create('wp/order', $request_body);
// Handle response
return $this->handle_response($response);
}
/**
* Try to cancel order.
*
* @since 1.2.0
*
* @return bool
*/
private function cancel_order()
{
// Set some order details
$request_body['wp_order_number'] = $this->order->get_id();
// Send to API
$response = $this->create('wp/cancel', $request_body);
// Handle response
return $this->handle_response($response);
}
/**
* Method to get order status in API.
*
* @since 1.0.0
*/
private function read_order_status()
{
// Build body
$request_body = [
'wp_order_numbers' => [$this->order->get_id()],
];
// Send to API
$response = $this->create('wp/status', $request_body);
// Handle response
if ($this->handle_response($response)) {
// Get body data
$response_body = (object) json_decode(wp_remote_retrieve_body($response));
// Get first item from data
$first_item = (object) reset($response_body->data);
// Return the status string of the first item
return $first_item->status;
} else {
return 'error';
}
}
/**
* Method to get status of multiple orders.
*
*
* @return array
*
* @since 1.0.0
*/
private function read_orders_status(array $order_ids)
{
// Build body data
$request_body = [
'wp_order_numbers' => $order_ids,
];
// Send to API
$response = $this->create('wp/status', $request_body);
// Handle response
if ($this->handle_response($response)) {
// Get body data
$response_body = (object) json_decode(wp_remote_retrieve_body($response));
// Start empty array
$orders_status = [];
// Loop all received data
foreach ($response_body->data as $order) {
$orders_status[$order->wp_order_number] = sanitize_text_field($order->status);
}
// Return array of order id with status attached
return $orders_status;
} else {
// Return empty array to prevent errors upstream
return [];
}
}
}

View file

@ -0,0 +1,191 @@
<?php
/*
* PARTNERCENTER API
*/
if (! defined('ABSPATH')) {
exit;
}
/*
* Load traits
*/
require_once 'partnercenter-api-requests/methods.php';
require_once 'partnercenter-api-requests/auth.php';
require_once 'partnercenter-api-requests/order.php';
/**
* Class PARTNERCENTER_API_Requests
*/
class PARTNERCENTER_API_Requests
{
use PARTNERCENTER_API_Auth;
// Load requests for orders
use PARTNERCENTER_API_Methods;
use PARTNERCENTER_API_Order;
/**
* Array of http headers.
*
* @since 1.0.0
*
* @var array Request headers
*/
private $headers;
/**
* Hard codes url of API.
*
* @since 1.0.0
*
* @var string
*/
private $api_url;
/**
* PARTNERCENTER_API_Requests constructor.
*/
public function __construct()
{
$this->api_url = 'https://api.gruene-seele.bio/';
// Reset request basics
$this->reset_request();
}
/**
* Helper to get headers for requests.
*
* @return array
*
* @since 1.0.0
*/
private function get_headers()
{
return $this->headers;
}
/**
* Handle new orders.
*
*
* @return bool
*
* @since 1.0.0
*/
public function new_order(int $order_id)
{
// Setup the order object
$this->setup_order($order_id);
// Handle new order data
return $this->write_order();
}
/**
* Handle order updates.
*
*
* @return bool
*
* @since 1.0.0
*/
public function update_order(int $order_id)
{
// Setup the order object
$this->setup_order($order_id);
// Handle new order data
return $this->write_order_changes();
}
/**
* Handle order status questions.
*
*
* @return string
*
* @since 1.0.0
*/
public function get_order_status(int $order_id)
{
// Setup the order object
$this->setup_order($order_id);
// Get and return the status string
return $this->read_order_status();
}
/**
* Public method to check status for a list of orders.
*
*
* @return array
*
* @since 1.0.0
*/
public function get_orders_status(array $order_ids)
{
return $this->read_orders_status($order_ids);
}
/**
* Handle button click to send data to PARTNERCENTER.
*
*
* @return bool
*
* @since 1.0.0
*/
public function send_order_items(int $order_id)
{
// Setup the order object
$this->setup_order($order_id);
// Send data to API
if (! $this->write_order_items()) {
// Return error state
return false;
}
// Add timestamp to order meta to store success
update_post_meta($order_id, '_partnercenter_api_order_fulfillment_forwarded', time());
// Return a success flag
return true;
}
/**
* Handle retrieving of order from PARTNERCENTER.
*
* @since 1.2.0
*
* @return bool
*/
public function retrieve_order(int $order_id)
{
// Setup the order object
$this->setup_order($order_id);
// Send data to API
if (! $this->cancel_order()) {
// Return error state
return false;
}
// Add timestamp to order meta to store success
delete_post_meta($order_id, '_partnercenter_api_order_fulfillment_forwarded');
// Return a success flag
return true;
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* Add Shortcode
*
* @since 1.0.0
*/
function partnercenter_api_testing_shortcode()
{
// return '<h3>PARTNERCENTER API Test</h3>';
// $order_id = 82990;
// $order = new WC_Order( $order_id );
// return var_export( $order, true );
// $url = $invoice->get_pdf_url();
// return 'Test ' . $order_id . ': ' . $order->get_meta( '_billing_title', true ) . ' || ' . $order->get_b;
$partnercenter = apply_filters('PARTNERCENTER_API', false);
// error_log( var_export( '$partnercenter', true ) );
// error_log( var_export( $partnercenter, true ) );
// $order = new WC_Order( 82990 );
// error_log( var_export( '$order 82990', true ) );
// error_log( var_export( $order, true ) );
// $new_order = $partnercenter->new_order( 82990 );
// error_log( var_export( '$new_order', true ) );
// error_log( var_export( $new_order, true ) );
// $update_order = $partnercenter->update_order( 58 );
// error_log( var_export( '$update_order', true ) );
// error_log( var_export( $update_order, true ) );
// $order_status = $partnercenter->get_order_status( 58 );
// error_log( var_export( '$order_status', true ) );
// error_log( var_export( $order_status, true ) );
// $orders_status = $partnercenter->read_orders_status([67,]);
// error_log( var_export( '$orders_status', true ) );
// error_log( var_export( $orders_status, true ) );
// $order_items = $partnercenter->send_order_items( 58 );
// error_log( var_export( '$order_items', true ) );
// error_log( var_export( $order_items, true ) );
// Test login and logout
// $logout = $partnercenter->logout();
// error_log( var_export( '$logout', true ) );
// error_log( var_export( $logout, true ) );
}
add_shortcode('partnercenter_api', 'partnercenter_api_testing_shortcode');

View file

@ -0,0 +1,282 @@
<?php
/*
* All order status related features.
*/
if (! defined('ABSPATH')) {
exit;
}
class PARTNERCENTER_API_WooCommerce_Order_Status_Class
{
/**
* PARTNERCENTER_API_WooCommerce_Order_Status_Class constructor.
*/
public function __construct()
{
// Listen to new orders
add_action('woocommerce_new_order', [$this, 'woocommerce_new_order'], 94839877, 2);
// Listen to order updates
add_action('woocommerce_update_order', [$this, 'woocommerce_update_order'], 94839877, 2);
// Listen to new orders by admin
add_action('woocommerce_process_shop_order_meta', [$this, 'woocommerce_process_shop_order_meta'], 9876, 1);
// Listen to button click
add_action('partnercenter_api_send_order_items', [$this, 'partnercenter_api_send_order_items'], 20, 1);
// Listen to button click
add_action('partnercenter_api_retrieve_order', [$this, 'partnercenter_api_retrieve_order'], 20, 1);
// Listen for cron event
add_action('partnercenter_api_fetch_orders_status', [$this, 'partnercenter_api_fetch_orders_status'], 20);
}
/**
* Run API request on new order.
*
* @param WC_Order order
*
* @since 1.0.0
*/
public function woocommerce_new_order(int $order_id, $order)
{
// Build a hash over addresses
$address_string = $order->get_formatted_billing_address().$order->get_formatted_shipping_address();
$new_address_hash = md5($address_string);
// Store hash of addresses as meta data
add_post_meta($order_id, '_partnercenter_api_address_hash', $new_address_hash, true);
// Load API
$partnercenter = apply_filters('PARTNERCENTER_API', false);
if ($partnercenter === false) {
return;
}
// Notify PARTNERCENTER about new order
$added = $partnercenter->new_order($order_id);
if ($added) {
// Add flag about sent order
update_post_meta($order_id, '_partnercenter_api_order_added', time());
}
}
/**
* Handle new orders from admin screen.
*
* @param $order_id integer
*/
public function woocommerce_process_shop_order_meta(int $order_id)
{
// Check if order needs handling- if value isset the order has been send to API on "thank you" page
if (! empty(get_post_meta($order_id, '_partnercenter_api_order_added', true))) {
return;
}
// Get order object
$order = new WC_Order($order_id);
// Check if email was given
if (empty($order->get_billing_email())) {
return;
}
// Load API
$partnercenter = apply_filters('PARTNERCENTER_API', false);
if ($partnercenter === false) {
return;
}
// Notify PARTNERCENTER about new order
$added = $partnercenter->new_order($order_id);
if ($added) {
// Add flag about sent order
update_post_meta($order_id, '_partnercenter_api_order_added', time());
}
}
/**
* Run API request on order update.
*
* @param WC_Order $order
*
* @since 1.0.0
*/
public function woocommerce_update_order(int $order_id, $order)
{
// Build a hash over addresses
$address_string = $order->get_formatted_billing_address().$order->get_formatted_shipping_address();
$new_address_hash = md5($address_string);
// Get old address hash
$old_address_hash = get_post_meta($order_id, '_partnercenter_api_address_hash', true);
// Check if address data has changed
if ($old_address_hash === $new_address_hash) {
// Address had not been changed
return;
}
// Update hash of addresses as meta data
update_post_meta($order_id, '_partnercenter_api_address_hash', $new_address_hash);
// Load API
$partnercenter = apply_filters('PARTNERCENTER_API', false);
if ($partnercenter === false) {
return;
}
// Notify PARTNERCENTER API about updated addresses
$partnercenter->update_order($order_id);
}
/**
* Run API request on order forwarding form action.
*
*
* @since 1.0.0
*/
public function partnercenter_api_send_order_items(int $order_id)
{
// Load API
$partnercenter = apply_filters('PARTNERCENTER_API', false);
if ($partnercenter === false) {
return;
}
// Notify PARTNERCENTER about order content
$received = $partnercenter->send_order_items($order_id);
// Exit on error
if ($received === false) {
return;
}
// Add a value to be overwritten by cron job
add_post_meta($order_id, '_partnercenter_api_order_status', 'open', true);
/*
* Add order ID to list of orders to check regularly
*/
// Get list of orders to add to
$unclear_orders = get_option('_partnercenter_api_unclear_order_ids', []);
// Add current order ID to list
$unclear_orders[] = intval($order_id);
// Save new list of unclear order ids
update_option('_partnercenter_api_unclear_order_ids', $unclear_orders, false);
}
/**
* Run API request to retrieve order from shipment.
*
*
* @since 1.2.0
*/
public function partnercenter_api_retrieve_order(int $order_id)
{
// Load API
$partnercenter = apply_filters('PARTNERCENTER_API', false);
if ($partnercenter === false) {
return;
}
// Notify PARTNERCENTER about retrieved order
$received = $partnercenter->retrieve_order($order_id);
// Exit on error
if ($received === false) {
return;
}
// Remove value to make order free again
delete_post_meta($order_id, '_partnercenter_api_order_status');
// Mark order to be canceled in API
update_post_meta($order_id, '_partnercenter_api_order_canceled', time());
/*
* Add order ID to list of orders to check regularly
*/
// Get list of orders to add to
$unclear_orders = get_option('_partnercenter_api_unclear_order_ids', []);
$found_key = array_search($order_id, $unclear_orders);
if ($found_key === false) {
return;
}
// Remove order from list of orders to check
unset($unclear_orders[$found_key]);
// Save new list of unclear order ids
update_option('_partnercenter_api_unclear_order_ids', $unclear_orders, false);
}
/**
* Handle cron event call.
*
* @since 1.0.0
*/
public function partnercenter_api_fetch_orders_status()
{
// Get list of unclear orders
$unclear_orders = get_option('_partnercenter_api_unclear_order_ids', []);
if (empty($unclear_orders)) {
// No orders to check
return;
}
// Load API
$partnercenter = apply_filters('PARTNERCENTER_API', false);
if ($partnercenter === false) {
return;
}
// Ask PARTNERCENTER about current status
$new_statuses = $partnercenter->get_orders_status($unclear_orders);
if (empty($new_statuses)) {
// No orders to check or connection error
return;
}
// Loop all received order statues
foreach ($new_statuses as $order_key => $order_status) {
// Store new status
update_post_meta($order_key, '_partnercenter_api_order_status', sanitize_title($order_status));
if (! in_array($order_status, ['close', 'sent'])) {
continue;
}
// Search order ID in list of unclear orders
$key_in_array = array_search($order_key, $unclear_orders);
// Remove order after it shipped
unset($unclear_orders[$key_in_array]);
}
// Save new list of unclear order IDs
update_option('_partnercenter_api_unclear_order_ids', $unclear_orders, false);
}
}
new PARTNERCENTER_API_WooCommerce_Order_Status_Class;