'', 'export' => 'invoices', 'sample' => 'settings', 'peppol' => 'settings?tab=peppol', ); /** Company detail fields: key => label. */ public static function fields() { return array( 'name' => __( 'Company name', 'peppolkit' ), 'street' => __( 'Street and number', 'peppolkit' ), 'postalCode' => __( 'Postcode', 'peppolkit' ), 'city' => __( 'City', 'peppolkit' ), 'country' => __( 'Country', 'peppolkit' ), 'vatNumber' => __( 'VAT number', 'peppolkit' ), 'registrationNumber' => __( 'KvK or enterprise number', 'peppolkit' ), 'email' => __( 'Invoice e-mail (reply-to)', 'peppolkit' ), 'iban' => __( 'IBAN', 'peppolkit' ), 'invoicePrefix' => __( 'Invoice number prefix (optional)', 'peppolkit' ), ); } public static function init() { add_action( 'admin_menu', array( __CLASS__, 'menu' ) ); add_action( 'admin_notices', array( __CLASS__, 'notices' ) ); add_action( 'admin_post_peppolkit_connect', array( __CLASS__, 'handle_connect' ) ); add_action( 'admin_post_peppolkit_settings', array( __CLASS__, 'handle_settings' ) ); add_action( 'admin_post_peppolkit_peppol', array( __CLASS__, 'handle_peppol' ) ); add_action( 'admin_post_peppolkit_plan', array( __CLASS__, 'handle_plan' ) ); add_action( 'admin_post_peppolkit_dashboard', array( __CLASS__, 'handle_dashboard' ) ); add_action( 'admin_post_peppolkit_requeue', array( __CLASS__, 'handle_requeue' ) ); add_action( 'admin_post_peppolkit_backfill', array( __CLASS__, 'handle_backfill' ) ); add_action( 'admin_post_peppolkit_disconnect', array( __CLASS__, 'handle_disconnect' ) ); add_filter( 'plugin_action_links_' . plugin_basename( PEPPOLKIT_FILE ), array( __CLASS__, 'action_links' ) ); } public static function menu() { add_menu_page( 'PeppolKit', 'PeppolKit', 'manage_woocommerce', 'peppolkit', array( __CLASS__, 'overview' ), self::menu_icon(), 56 ); add_submenu_page( 'peppolkit', __( 'PeppolKit overview', 'peppolkit' ), __( 'Overview', 'peppolkit' ), 'manage_woocommerce', 'peppolkit', array( __CLASS__, 'overview' ) ); add_submenu_page( 'peppolkit', __( 'PeppolKit settings', 'peppolkit' ), __( 'Settings', 'peppolkit' ), 'manage_woocommerce', 'peppolkit-settings', array( __CLASS__, 'page' ) ); } /** Monochrome PeppolKit mark for the admin menu (WordPress tints it). */ private static function menu_icon() { return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHBhdGggZmlsbD0iYmxhY2siIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTE1IDBoMzRhMTUgMTUgMCAwIDEgMTUgMTV2MzRhMTUgMTUgMCAwIDEtMTUgMTVIMTVBMTUgMTUgMCAwIDEgMCA0OVYxNUExNSAxNSAwIDAgMSAxNSAwek0xOSAxMmgxN2wxMSAxMXY4YTEyIDEyIDAgMCAxLTEyIDEyaC02djZhNCA0IDAgMCAxLTQgNGgtMmE0IDQgMCAwIDEtNC00VjE2YTQgNCAwIDAgMSA0LTR6TTI5IDIzdjEyaDVhNiA2IDAgMCAwIDAtMTJ6Ii8+PC9zdmc+'; } /** Prints the page title with the PeppolKit mark. */ private static function heading( $title ) { printf( '

%s

', esc_url( plugins_url( 'assets/logo.svg', PEPPOLKIT_FILE ) ), esc_html( $title ) ); } public static function action_links( $links ) { array_unshift( $links, '' . esc_html__( 'Settings', 'peppolkit' ) . '' ); return $links; } private static function back( $args, $page = 'peppolkit-settings' ) { wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php?page=' . $page ) ) ); exit; } private static function guard( $action ) { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'Not allowed.', 'peppolkit' ) ); } check_admin_referer( $action ); } /** One-shot admin notice for the current user, shown on the next page load. */ public static function flash( $type, $message ) { set_transient( 'peppolkit_notice_' . get_current_user_id(), array( $type, $message ), 120 ); } /** Small POST form with a single button. */ /** Prints a small POST form with a single button. */ private static function button( $action, $label, $class = 'button', $hidden = array() ) { printf( '
', esc_url( admin_url( 'admin-post.php' ) ) ); wp_nonce_field( $action ); printf( '', esc_attr( $action ) ); foreach ( $hidden as $k => $v ) { printf( '', esc_attr( $k ), esc_attr( $v ) ); } printf( '
', esc_attr( $class ), esc_html( $label ) ); } public static function notices() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } $key = 'peppolkit_notice_' . get_current_user_id(); $flash = get_transient( $key ); if ( is_array( $flash ) ) { delete_transient( $key ); printf( '

%s

', esc_attr( 'error' === $flash[0] ? 'error' : 'success' ), esc_html( $flash[1] ) ); } if ( ! PeppolKit_Api::connected() || ! self::own_screen() ) { return; } $pending = PeppolKit_Orders::pending_count(); if ( $pending > 0 ) { echo '

' . esc_html( sprintf( /* translators: %d: number of orders */ _n( '%d order is waiting for a PeppolKit invoice (invoice limit reached or company details missing).', '%d orders are waiting for a PeppolKit invoice (invoice limit reached or company details missing).', $pending, 'peppolkit' ), $pending ) ) . '

'; self::button( 'peppolkit_requeue', __( 'Create invoices now', 'peppolkit' ), 'button button-primary' ); echo ' ' . esc_html__( 'Settings and plan', 'peppolkit' ) . '

'; } } /** The waiting-orders notice only shows on the PeppolKit pages and the WooCommerce order screens. */ private static function own_screen() { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen ) { return false; } return false !== strpos( $screen->id, 'peppolkit' ) || in_array( $screen->id, array( 'edit-shop_order', 'shop_order', 'woocommerce_page_wc-orders' ), true ); } public static function handle_connect() { self::guard( 'peppolkit_connect' ); $res = PeppolKit_Api::connect( sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). self::back( is_wp_error( $res ) ? array( 'pk_error' => rawurlencode( $res->get_error_message() ) ) : array( 'pk_ok' => 'connected' ) ); } public static function handle_settings() { self::guard( 'peppolkit_settings' ); $body = array(); foreach ( array_keys( self::fields() ) as $k ) { $v = sanitize_text_field( wp_unslash( $_POST[ $k ] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). if ( '' !== $v ) { $body[ $k ] = $v; } } foreach ( array( 'ossRegistered', 'belowMicroThreshold', 'emailCustomers', 'peppolSend' ) as $k ) { $body[ $k ] = ! empty( $_POST[ $k ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). } $lang = sanitize_key( wp_unslash( $_POST['language'] ?? 'auto' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). $body['language'] = in_array( $lang, array( 'auto', 'nl', 'fr', 'en' ), true ) ? $lang : 'auto'; update_option( 'peppolkit_settings_cache', $body, false ); $res = PeppolKit_Api::request( 'PUT', '/settings', $body ); if ( is_wp_error( $res ) ) { self::back( array( 'pk_error' => rawurlencode( $res->get_error_message() ) ) ); } // Orders that waited for company details get their invoice now. PeppolKit_Orders::requeue_pending(); self::back( array( 'pk_ok' => 'saved' ) ); } public static function handle_peppol() { self::guard( 'peppolkit_peppol' ); $res = PeppolKit_Api::request( 'POST', '/peppol' ); if ( is_wp_error( $res ) ) { self::back( array( 'pk_error' => rawurlencode( $res->get_error_message() ) ) ); } if ( ! empty( $res['verifyUrl'] ) ) { wp_redirect( $res['verifyUrl'] ); // phpcs:ignore WordPress.Security.SafeRedirect -- identity check at the Peppol access point. exit; } self::back( array( 'pk_ok' => 'peppol_' . sanitize_key( $res['status'] ?? '' ) ) ); } /** * Plan names come from the API in Dutch (they are also its keys); show them in the admin language. * * @param string $name Plan key: Gratis, Starter, Groei or Pro. */ private static function plan_label( $name ) { $labels = array( 'Gratis' => __( 'Free', 'peppolkit' ), 'Groei' => __( 'Growth', 'peppolkit' ), ); return $labels[ $name ] ?? $name; } public static function handle_plan() { self::guard( 'peppolkit_plan' ); $plan = sanitize_text_field( wp_unslash( $_POST['plan'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). $res = PeppolKit_Api::request( 'POST', '/billing', array( 'plan' => $plan, 'redirectUrl' => admin_url( 'admin.php?page=peppolkit-settings&pk_ok=plan' ), ) ); if ( is_wp_error( $res ) ) { self::back( array( 'pk_error' => rawurlencode( $res->get_error_message() ) ) ); } if ( ! empty( $res['checkoutUrl'] ) ) { wp_redirect( $res['checkoutUrl'] ); // phpcs:ignore WordPress.Security.SafeRedirect -- Mollie checkout. exit; } // A plan change may lift the invoice limit. PeppolKit_Orders::requeue_pending(); self::back( array( 'pk_ok' => 'plan' ) ); } /** One-click login to the hosted PeppolKit dashboard. */ public static function handle_dashboard() { self::guard( 'peppolkit_dashboard' ); $target = sanitize_key( wp_unslash( $_POST['target'] ?? 'open' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). $page = self::DASHBOARD_PAGES[ $target ] ?? ''; $res = PeppolKit_Api::request( 'POST', '/dashboard-link?page=' . rawurlencode( $page ) ); if ( is_wp_error( $res ) || empty( $res['url'] ) ) { self::back( array( 'pk_error' => rawurlencode( is_wp_error( $res ) ? $res->get_error_message() : __( 'The dashboard is not available right now.', 'peppolkit' ) ) ), 'peppolkit' ); } wp_redirect( $res['url'] ); // phpcs:ignore WordPress.Security.SafeRedirect -- hosted PeppolKit dashboard. exit; } public static function handle_requeue() { self::guard( 'peppolkit_requeue' ); $n = PeppolKit_Orders::requeue_pending(); /* translators: %d: number of orders */ self::flash( 'success', sprintf( _n( '%d order queued for an invoice.', '%d orders queued for an invoice.', $n, 'peppolkit' ), $n ) ); self::back( array(), 'peppolkit' ); } public static function handle_backfill() { self::guard( 'peppolkit_backfill' ); $n = PeppolKit_Orders::backfill( 60, 250 ); /* translators: %d: number of orders */ self::flash( 'success', sprintf( _n( '%d paid order queued for an invoice.', '%d paid orders queued for an invoice.', $n, 'peppolkit' ), $n ) ); self::back( array(), 'peppolkit' ); } public static function handle_disconnect() { self::guard( 'peppolkit_disconnect' ); // Tell PeppolKit first: the account is removed 30 days later unless the store connects again. PeppolKit_Api::request( 'POST', '/disconnect' ); delete_option( PeppolKit_Api::OPTION_KEY ); delete_option( 'peppolkit_settings_cache' ); if ( function_exists( 'as_unschedule_all_actions' ) ) { as_unschedule_all_actions( '', array(), 'peppolkit' ); } self::flash( 'success', __( 'PeppolKit is disconnected. Your invoices stay available in your PeppolKit dashboard for 30 days; download them before then. Connect again within 30 days to keep everything.', 'peppolkit' ) ); self::back( array(), 'peppolkit' ); } private static function messages() { if ( isset( $_GET['pk_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification -- display only. echo '

' . esc_html( rawurldecode( sanitize_text_field( wp_unslash( $_GET['pk_error'] ) ) ) ) . '

'; // phpcs:ignore WordPress.Security.NonceVerification } $messages = array( 'connected' => __( 'Connected to PeppolKit. Now fill in your company details.', 'peppolkit' ), 'saved' => __( 'Saved. Paid orders now get an invoice automatically.', 'peppolkit' ), 'peppol_active' => __( 'Peppol is active.', 'peppolkit' ), 'peppol_unavailable' => __( 'Peppol sending is not available yet.', 'peppolkit' ), 'plan' => __( 'Plan updated. A paid plan becomes active as soon as Mollie confirms the payment.', 'peppolkit' ), ); $ok = sanitize_key( wp_unslash( $_GET['pk_ok'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification if ( isset( $messages[ $ok ] ) ) { echo '

' . esc_html( $messages[ $ok ] ) . '

'; } } private static function connect_form() { echo '

' . esc_html__( 'PeppolKit turns every paid order into an EU-compliant invoice with a Peppol e-invoice. Connect your shop to start (free up to 10 invoices a month).', 'peppolkit' ) . '

'; echo '
'; wp_nonce_field( 'peppolkit_connect' ); echo ''; echo '

'; submit_button( __( 'Connect to PeppolKit', 'peppolkit' ) ); echo '
'; } /** The free plan sends over Peppol through the merchant's own free Peppyrus account, not through PeppolKit's partner. */ private static function sends_via_own_peppyrus( $plan ) { return is_array( $plan ) && 0 === (int) ( $plan['peppolIncluded'] ?? 0 ); } private static function usage_bar( $label, $used, $limit ) { $used = (int) $used; echo '

' . esc_html( $label ) . ' '; if ( null === $limit ) { /* translators: %d: count used */ echo esc_html( sprintf( __( '%d (unlimited)', 'peppolkit' ), $used ) ) . '

'; return; } $limit = max( 0, (int) $limit ); /* translators: 1: count used, 2: limit */ echo esc_html( sprintf( __( '%1$d of %2$d', 'peppolkit' ), $used, $limit ) ) . '

'; $pct = $limit > 0 ? min( 100, (int) round( 100 * $used / $limit ) ) : 100; $color = $pct >= 100 ? '#d63638' : ( $pct >= 80 ? '#dba617' : '#2271b1' ); printf( '
', (int) $pct, esc_attr( $color ) ); } /** Overview: setup checklist, usage, latest invoices, dashboard shortcuts. */ public static function overview() { echo '
'; self::heading( __( 'PeppolKit overview', 'peppolkit' ) ); self::messages(); if ( ! PeppolKit_Api::connected() ) { self::connect_form(); echo '
'; return; } $remote = PeppolKit_Api::request( 'GET', '/settings' ); $remote = is_wp_error( $remote ) ? array() : $remote; $list = PeppolKit_Api::request( 'GET', '/invoices' ); $invoices = is_wp_error( $list ) ? array() : array_slice( (array) ( $list['invoices'] ?? array() ), 0, 10 ); $usage = (array) ( $remote['usage'] ?? array() ); echo '

'; self::button( 'peppolkit_dashboard', __( 'Open dashboard', 'peppolkit' ), 'button button-primary', array( 'target' => 'open' ) ); self::button( 'peppolkit_dashboard', __( 'Monthly export', 'peppolkit' ), 'button', array( 'target' => 'export' ) ); self::button( 'peppolkit_dashboard', __( 'Sample invoice', 'peppolkit' ), 'button', array( 'target' => 'sample' ) ); echo '

'; echo '

' . esc_html__( 'Setup', 'peppolkit' ) . '

'; if ( ! empty( $remote['plan'] ) ) { $plan = $remote['plan']; /* translators: %s: plan name */ echo '

' . esc_html( sprintf( __( 'Usage this month (plan %s)', 'peppolkit' ), self::plan_label( $plan['name'] ) ) ) . '

'; if ( $usage ) { self::usage_bar( __( 'Invoices', 'peppolkit' ), $usage['invoicesThisMonth'] ?? 0, array_key_exists( 'invoicesPerMonth', $plan ) ? $plan['invoicesPerMonth'] : null ); if ( self::sends_via_own_peppyrus( $plan ) ) { echo '

' . esc_html__( 'Peppol', 'peppolkit' ) . ' ' . esc_html__( 'is sent through your own free Peppyrus account and is not counted here.', 'peppolkit' ) . '

'; } else { self::usage_bar( __( 'Peppol sends', 'peppolkit' ), $usage['peppolThisMonth'] ?? 0, (int) ( $plan['peppolIncluded'] ?? 0 ) ); } } else { echo '

' . esc_html__( 'Usage figures are not available yet.', 'peppolkit' ) . '

'; } } echo '

' . esc_html__( 'Latest invoices', 'peppolkit' ) . '

'; if ( ! $invoices ) { echo '

' . esc_html__( 'No invoices yet. They are created as soon as an order is paid.', 'peppolkit' ) . '

'; } else { echo ''; foreach ( $invoices as $inv ) { $order_id = (int) strtok( (string) ( $inv['orderId'] ?? '' ), '/' ); $order = $order_id ? wc_get_order( $order_id ) : false; echo '', esc_html( $inv['issueDate'] ?? '' ), wp_kses_post( wc_price( (float) $inv['total'], array( 'currency' => $inv['currency'] ?? '' ) ) ), esc_html( PeppolKit_Orders::peppol_label( $inv['peppolStatus'] ?? null ) ), esc_html( PeppolKit_Orders::email_label( $inv ) ), esc_url( $inv['pdfUrl'] ) ); } echo '
' . esc_html__( 'Number', 'peppolkit' ) . '' . esc_html__( 'Order', 'peppolkit' ) . '' . esc_html__( 'Date', 'peppolkit' ) . '' . esc_html__( 'Total', 'peppolkit' ) . 'Peppol' . esc_html__( 'E-mail', 'peppolkit' ) . 'PDF
' . esc_html( ( 'credit_note' === ( $inv['type'] ?? '' ) ? __( 'Credit note', 'peppolkit' ) . ' ' : '' ) . $inv['number'] ) . ''; if ( $order ) { printf( '#%s', esc_url( $order->get_edit_order_url() ), esc_html( $order->get_order_number() ) ); } else { echo esc_html( (string) ( $inv['orderId'] ?? '' ) ); } printf( '%s%s%s%sPDF
'; echo '

'; self::button( 'peppolkit_dashboard', __( 'All invoices in the dashboard', 'peppolkit' ), 'button-link', array( 'target' => 'export' ) ); echo '

'; } echo '

' . esc_html__( 'Earlier orders', 'peppolkit' ) . '

' . esc_html__( 'Paid orders from before you connected PeppolKit have no invoice yet. This queues up to 250 of them.', 'peppolkit' ) . '

'; self::button( 'peppolkit_backfill', __( 'Create invoices for paid orders of the last 60 days', 'peppolkit' ) ); echo ''; } /** * The hosted dashboard can edit the same settings: when the server is configured its values win, * and the local copy is updated to match. Keys the server does not return keep the local value. */ private static function sync_from_server( $remote ) { $cache = (array) get_option( 'peppolkit_settings_cache', array() ); if ( is_wp_error( $remote ) || empty( $remote['configured'] ) ) { return $cache; } $seller = (array) ( $remote['seller'] ?? array() ); $addr = (array) ( $seller['address'] ?? array() ); $server = array( 'name' => $seller['name'] ?? null, 'street' => $addr['street'] ?? null, 'postalCode' => $addr['postalCode'] ?? null, 'city' => $addr['city'] ?? null, 'country' => $addr['country'] ?? null, 'vatNumber' => $seller['vatNumber'] ?? null, 'registrationNumber' => $seller['registrationNumber'] ?? null, 'email' => $seller['email'] ?? null, 'iban' => $remote['iban'] ?? ( $seller['iban'] ?? null ), 'invoicePrefix' => $remote['invoicePrefix'] ?? ( $seller['invoicePrefix'] ?? null ), 'language' => $remote['language'] ?? null, ); foreach ( array( 'ossRegistered', 'belowMicroThreshold', 'emailCustomers', 'peppolSend' ) as $k ) { $server[ $k ] = isset( $remote[ $k ] ) ? (bool) $remote[ $k ] : null; } $merged = array_merge( $cache, array_filter( $server, function ( $v ) { return null !== $v; } ) ); if ( $merged !== $cache ) { update_option( 'peppolkit_settings_cache', $merged, false ); } return $merged; } private static function plans_section() { $b = PeppolKit_Api::request( 'GET', '/billing' ); if ( is_wp_error( $b ) || empty( $b['available'] ) ) { return; } echo '

' . esc_html__( 'Plan', 'peppolkit' ) . '

' . esc_html( sprintf( /* translators: %s: plan */ __( 'Current plan: %s', 'peppolkit' ), $b['current'] ) ) . '

'; foreach ( $b['plans'] as $p ) { echo ''; echo ''; } echo '
' . esc_html( self::plan_label( $p['name'] ) ) . '' . esc_html( null === $p['invoicesPerMonth'] ? __( 'Unlimited invoices', 'peppolkit' ) : sprintf( /* translators: %d: invoices */ __( '%d invoices / month', 'peppolkit' ), $p['invoicesPerMonth'] ) ) . '' . esc_html( (int) $p['peppolIncluded'] > 0 ? sprintf( /* translators: %d: sends */ __( '%d Peppol sends', 'peppolkit' ), $p['peppolIncluded'] ) : __( 'Peppol via your own Peppyrus account', 'peppolkit' ) ) . '€ ' . esc_html( number_format_i18n( $p['gross'] / 100, 2 ) ) . ( $p['vat'] ? ' ' . esc_html__( 'incl. VAT', 'peppolkit' ) : '' ) . ' / ' . esc_html__( 'month', 'peppolkit' ) . ''; if ( $p['name'] !== $b['current'] ) { self::button( 'peppolkit_plan', __( 'Choose', 'peppolkit' ), 'button', array( 'plan' => $p['name'] ) ); } echo '
'; if ( 'Gratis' !== $b['current'] ) { echo '

'; self::button( 'peppolkit_plan', __( 'Back to the free plan', 'peppolkit' ), 'button-link', array( 'plan' => 'Gratis' ) ); echo '

'; } } public static function page() { echo '
'; self::heading( __( 'PeppolKit settings', 'peppolkit' ) ); self::messages(); if ( ! PeppolKit_Api::connected() ) { self::connect_form(); echo '
'; return; } $remote = PeppolKit_Api::request( 'GET', '/settings' ); $cache = self::sync_from_server( $remote ); $val = function ( $k ) use ( $cache ) { return (string) ( $cache[ $k ] ?? '' ); }; $flag = function ( $k, $default ) use ( $cache ) { return isset( $cache[ $k ] ) ? (bool) $cache[ $k ] : $default; }; if ( ! is_wp_error( $remote ) && ! empty( $remote['plan'] ) ) { if ( self::sends_via_own_peppyrus( $remote['plan'] ) ) { printf( '

%s

', esc_html( sprintf( /* translators: %s: plan name */ __( 'Plan: %s · Peppol goes through your own free Peppyrus account.', 'peppolkit' ), self::plan_label( $remote['plan']['name'] ) ) ) ); } else { printf( '

%s

', esc_html( sprintf( /* translators: 1: plan name, 2: Peppol sends included */ __( 'Plan: %1$s · %2$d Peppol sends a month included.', 'peppolkit' ), self::plan_label( $remote['plan']['name'] ), (int) $remote['plan']['peppolIncluded'] ) ) ); } } self::plans_section(); echo '

' . esc_html__( 'Company details', 'peppolkit' ) . '

'; echo '
'; wp_nonce_field( 'peppolkit_settings' ); echo ''; foreach ( self::fields() as $k => $label ) { if ( 'country' === $k ) { $cur = strtoupper( (string) $val( $k ) ); printf( ''; continue; } printf( '', esc_attr( $k ), esc_html( $label ), esc_attr( $val( $k ) ) ); } $check = function ( $name, $label, $on ) { printf( '', esc_attr( $name ), checked( $on, true, false ), esc_html( $label ) ); }; $check( 'emailCustomers', __( 'E-mail the invoice to the customer', 'peppolkit' ), $flag( 'emailCustomers', true ) ); $check( 'peppolSend', __( 'Send invoices to business customers over Peppol', 'peppolkit' ), $flag( 'peppolSend', true ) ); $check( 'ossRegistered', __( 'Registered for the EU One-Stop Shop (OSS)', 'peppolkit' ), $flag( 'ossRegistered', false ) ); $check( 'belowMicroThreshold', __( 'Below €10,000 EU distance-sales threshold', 'peppolkit' ), $flag( 'belowMicroThreshold', false ) ); echo '
' . esc_html__( 'Invoice language', 'peppolkit' ) . '
'; submit_button( __( 'Save', 'peppolkit' ) ); echo '
'; echo '

' . esc_html__( 'Invoice design', 'peppolkit' ) . '

' . esc_html__( 'Choose one of six designs, add your logo and pick your brand colour in the PeppolKit dashboard. They apply to every invoice.', 'peppolkit' ) . '

'; self::button( 'peppolkit_dashboard', __( 'Customise invoice design', 'peppolkit' ), 'button', array( 'target' => 'sample' ) ); echo '

Peppol

'; $via_peppyrus = ! is_wp_error( $remote ) && 'peppyrus' === ( $remote['peppolVia'] ?? '' ); $active = ! is_wp_error( $remote ) && ! empty( $remote['peppolActive'] ); if ( ! is_wp_error( $remote ) && ! empty( $remote['plan'] ) && self::sends_via_own_peppyrus( $remote['plan'] ) ) { if ( ! ( $via_peppyrus && $active ) ) { echo '

' . esc_html__( 'Belgian businesses must receive invoices over Peppol. On the free plan you send them through your own free Peppyrus account: create the account at Peppyrus, then paste its API key in the PeppolKit dashboard. Invoices to consumers never go over Peppol.', 'peppolkit' ) . '

'; } if ( $via_peppyrus && $active ) { echo '

' . esc_html( sprintf( /* translators: %s: Peppol participant ID */ __( 'Connected: sending over Peppol through your Peppyrus account (%s).', 'peppolkit' ), $remote['peppyrusParticipant'] ?? '' ) ) . '

'; } else { echo '

'; self::button( 'peppolkit_dashboard', __( 'Connect Peppyrus in the dashboard', 'peppolkit' ), 'button button-primary', array( 'target' => 'peppol' ) ); echo ' ' . esc_html__( 'Step-by-step guide', 'peppolkit' ) . '

'; } } else { echo '

' . esc_html__( 'Belgian businesses must receive invoices over Peppol. Activate once; an authorised person confirms your company\'s identity at our Peppol partner.', 'peppolkit' ) . '

'; if ( $via_peppyrus && $active ) { echo '

' . esc_html( sprintf( /* translators: %s: Peppol participant ID */ __( 'Connected: sending over Peppol through your Peppyrus account (%s).', 'peppolkit' ), $remote['peppyrusParticipant'] ?? '' ) ) . '

'; } else { if ( $active ) { echo '

' . esc_html__( 'Peppol registration started.', 'peppolkit' ) . '

'; } echo '
'; wp_nonce_field( 'peppolkit_peppol' ); echo ''; submit_button( __( 'Activate Peppol / confirm identity', 'peppolkit' ), 'secondary' ); echo '
'; } } echo '

' . esc_html__( 'Disconnect', 'peppolkit' ) . '

' . esc_html__( 'Removes the API key from this shop. New orders no longer get an invoice; existing invoices stay in your PeppolKit account.', 'peppolkit' ) . '

'; echo '
'; wp_nonce_field( 'peppolkit_disconnect' ); echo '
'; } }