<?php
|
|
namespace JVBase\integrations\services;
|
use JVBase\base\Options;
|
use JVBase\integrations\Admin;
|
use JVBase\Instance;
|
use JVBase\integrations\APIEndpoint;
|
use JVBase\integrations\Integrations;
|
use JVBase\integrations\OAuth;
|
use JVBase\integrations\OAuthCredentials;
|
use JVBase\integrations\Requests;
|
use JVBase\integrations\SyncToPost;
|
use JVBase\integrations\UserConnection;
|
use JVBase\integrations\Webhooks;
|
use JVBase\managers\Dashboard\Dashboard;
|
use JVBase\managers\IconsManager;
|
use JVBase\registrar\Registrar;
|
use JVBase\ui\CRUDSkeleton;
|
use WP_Error;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
/**
|
* Helcim Integration Class
|
*
|
* Handles HelcimPay.js checkout, invoice retrieval, customer/card management
|
*
|
* Helcim does not have a Product/Service API, so we utilize the Invoice api and add
|
* products and services as line items.
|
*
|
* Helcim does have a nice Recurring API, which can be used for subscriptions
|
* as well as handling payment plans.
|
*
|
* Customers can sync as well, through the Customer API.
|
*
|
* @since 1.0.0
|
*/
|
class Helcim extends Integrations
|
{
|
use Admin, Instance, Requests, UserConnection, SyncToPost, Webhooks;
|
|
protected string $orderPostType = 'hm_invoice';
|
protected array $orderLabel = ['Order', 'Orders'];
|
protected string $productPostType = 'hm_product';
|
protected array $productLabel = ['Product', 'Products'];
|
protected string $servicePostType = 'hm_service';
|
protected array $serviceLabel = ['Service', 'Services'];
|
protected string $subscriptionPostType = 'hm_subscription';
|
protected array $subscriptionLabel = ['Active Subscription', 'Active Subscriptions'];
|
protected string $quotePostType = 'hm_quote';
|
protected array $quoteLabel = ['Quote', 'Quotes'];
|
protected string $addOnsPostType = 'hm_add_on';
|
protected array $addOnsLabel = ['Add On', 'Add Ons'];
|
protected string $paymentPlanPostType = 'hm_payment_plan';
|
protected array $paymentPlanLabel = ['Subscription', 'Subscriptions'];
|
|
protected function __construct()
|
{
|
$this->canTest = true;
|
$this->service_name = 'helcim';
|
$this->title = 'Helcim';
|
$this->icon = 'currency-dollar';
|
|
$this->canSync = [
|
'initial' => true,
|
'update' => true,
|
'delete' => true,
|
];
|
|
$this->apiVersion = 'v2';
|
$this->baseUrl = sprintf('https://api.helcim.com/%s/', $this->apiVersion);
|
|
$this->defineFields();
|
$this->defineInstructions();
|
|
parent::__construct();
|
}
|
protected function defineFields():void
|
{
|
$api_token = $this->addField(
|
'api_token',
|
'API Token',
|
'text',
|
[$this, 'testUserCanConnect']
|
);
|
$api_token->setSubType('password');
|
$api_token->setRequired();
|
$api_token->setHint('Found in Helcim Dashboard → Settings → API Access');
|
$api_token->makeSetupField();
|
$api_token->setHideStored();
|
$this->updateField('api_token', $api_token);
|
|
$verifier = $this->addField(
|
'verifier_token',
|
'Webhook Verifier Token',
|
'text',
|
[$this, 'testUserCanConnect']
|
);
|
$verifier->setSubType('password');
|
$verifier->setRequired();
|
$verifier->makeSetupField();
|
$verifier->setHideStored();
|
$this->updateField('verifier_token', $verifier);
|
|
$types = $this->addField(
|
'product_types',
|
'Product Types',
|
'checkbox',
|
[$this, 'testUserSetup']
|
);
|
$opts = [
|
'product' => 'Products',
|
'service' => 'Services',
|
'subscription' => 'Subscriptions',
|
'addon' => 'Add Ons',
|
'quote' => 'Quotes',
|
];
|
$types->makeAdditionalField();
|
$types->setOptions($opts);
|
$types->setDefault(implode(',',array_keys($opts)));
|
$this->updateField('product_types', $types);
|
|
$currency = $this->addField(
|
'currency',
|
'Currency',
|
'select',
|
[$this, 'testUserSetup']
|
);
|
$currency->setOptions([
|
'CAD' => 'CAD',
|
'USD' => 'USD'
|
]);
|
$currency->setDefault('CAD');
|
$currency->makeAdditionalField();
|
|
$feeSaver = $this->addField(
|
'fee_saver',
|
'Fee Saver',
|
'true_false',
|
[$this, 'testUserSetup']
|
);
|
$feeSaver->setHint('Pass processing fees to customers? (Not compatible with Google Pay)');
|
$feeSaver->makeAdditionalField();
|
|
$allowACH = $this->addField(
|
'allow_ach',
|
'Allow ACH/Bank Payments',
|
'true_false',
|
[$this, 'testUserSetup']
|
);
|
$allowACH->setHint('Enable bank account payments alongside credit card');
|
$allowACH->makeAdditionalField();
|
}
|
public function testUserCanConnect():bool
|
{
|
$user = get_current_user_id();
|
return $this->userCanConnect($user);
|
}
|
public function testUserSetup():bool
|
{
|
return $this->isSetUp() && $this->testUserCanConnect() && !empty($this->getApiToken());
|
}
|
|
protected function defineInstructions():void
|
{
|
$this->instructions = [
|
'Go to <a href="https://my.helcim.com" target="_blank">Helcim Dashboard</a>',
|
'Create an API Access Token:',
|
['Navigate to Settings → Integrations → API Access Configuration',
|
'Create a new API Access Configuration',
|
'Enable permissions: General (Customers, Invoices, Products), Transaction Processing',
|
'Copy the API Token and paste it below. This is hashed and stored securely',],
|
'Enable Webhooks:',
|
[
|
'Navigate to Settings → Integrations → Webhooks',
|
'Enable Webhooks',
|
'Enter the webhook URL below into the \'Deliver URL\' field',
|
'Save Changes',
|
'A verifier token will be generated. Copy and paste it into the Verifier Token field below',
|
'This is hashed and stored securely',
|
]
|
];
|
}
|
|
protected function defineActions():void
|
{
|
//Helcim does not currently have an API for product management, unfortunately.
|
// if ($this->isSetUp()) {
|
// $this->addAction(
|
// 'Sync to Helcim',
|
// [$this, 'syncToHelcim'],
|
// 'sync_to_helcim',
|
// 'upload-simple'
|
// );
|
// $this->addAction(
|
// 'Sync to Site',
|
// [$this, 'syncToSite'],
|
// 'sync_to_site',
|
// 'download-simple'
|
// );
|
// }
|
|
// if ($this->isSetUp()) {
|
// $this->addAction(
|
// 'Import All From Helcim',
|
// [$this, 'importFromHelcim'],
|
// 'import_from_helcim',
|
// 'download-simple'
|
// );
|
// $this->addAction(
|
// 'Import Orders From Helcim',
|
// [$this, 'importOrders'],
|
// 'import_orders',
|
// 'download-simple'
|
// );
|
// $this->addAction(
|
// 'Import Add Ons From Helcim',
|
// [$this, 'importAddOns'],
|
// 'import_add_ons',
|
// 'download-simple'
|
// );
|
// $this->addAction(
|
// 'Import Subscriptions From Helcim',
|
// [$this, 'importSubscriptions'],
|
// 'import_subscriptions',
|
// 'download-simple'
|
// );
|
// $this->addAction(
|
// 'Import Active Subscriptions From Helcim',
|
// [$this, 'importActiveSubscriptions'],
|
// 'import_active_subscriptions',
|
// 'download-simple'
|
// );
|
//
|
// $this->addAction(
|
// 'Sync All TO Helcim',
|
// [$this, 'syncToHelcim'],
|
// 'sync_to_helcim',
|
// 'upload-simple'
|
// );
|
// $this->addAction(
|
// 'Sync Orders TO Helcim',
|
// [$this, 'syncOrders'],
|
// 'sync_orders',
|
// 'upload-simple'
|
// );
|
// $this->addAction(
|
// 'Sync Add Ons TO Helcim',
|
// [$this, 'syncAddOns'],
|
// 'sync_add_ons',
|
// 'upload-simple'
|
// );
|
// $this->addAction(
|
// 'Sync Subscriptions TO Helcim',
|
// [$this, 'syncSubscriptions'],
|
// 'sync_subscriptions',
|
// 'upload-simple'
|
// );
|
// $this->addAction(
|
// 'Sync Active Subscribers TO Helcim',
|
// [$this, 'syncActiveSubscriptions'],
|
// 'sync_active_subscriptions',
|
// 'upload-simple'
|
// );
|
// }
|
|
}
|
protected function registerAdditionalHooks(): void
|
{
|
add_action('jvb_define_registrar', [$this, 'registerPostTypes'], 3);
|
// add_action('jvb_define_registrar', [$this, 'registerPostTypes'], 3);
|
add_action('jvb_define_fields', [$this, 'registerFields'], 4);
|
// add_action('jvb_define_fields', [$this, 'registerFields'], 4);
|
add_action('init', [$this, 'addDashboardPages']);
|
add_action(BASE.'dashboard_page_orders', [$this, 'renderDashPage']);
|
|
add_action('admin_head', [$this, 'add_separators']);
|
}
|
public function add_separators():void
|
{
|
echo '<style>
|
/* Hide the container link background/padding defaults and make it a divider line */
|
#adminmenu .wp-submenu li a[href*="page=sep-"] {
|
padding: 0 !important;
|
margin: 0;
|
border-top: 1px solid rgba(240, 246, 252, 0.2); /* Match dark mode border color */
|
height: 0;
|
overflow: hidden;
|
pointer-events: none; /* Make it unclickable */
|
cursor: default;
|
}
|
</style>';
|
}
|
|
protected function initialize(): void
|
{
|
$this->definePaymentEndpoints();
|
$this->defineInvoiceEndpoints();
|
$this->defineCustomerEndpoints();
|
$this->defineRecurringEndpoints();
|
}
|
|
protected function definePaymentEndpoints():void
|
{
|
$payment = new APIEndpoint();
|
|
$this->apiEndpoints['payment'] = $payment;
|
}
|
protected function defineInvoiceEndpoints():void
|
{
|
$invoice = new APIEndpoint();
|
|
$this->apiEndpoints['invoice'] = $invoice;
|
}
|
protected function defineCustomerEndpoints():void
|
{
|
$customer = new APIEndpoint();
|
$customer->setCreate('customers');
|
$customer->setImport('customers');
|
$customer->setUpdate('customers/customerId');
|
$this->apiEndpoints['customer'] = $customer;
|
}
|
protected function defineRecurringEndpoints():void
|
{
|
$recurring = new APIEndpoint();
|
|
$this->apiEndpoints['recurring'] = $recurring;
|
}
|
|
protected function getRequestHeaders(): array
|
{
|
return empty($this->getApiToken()) ? [] : [
|
'api-token' => $this->getApiToken(),
|
'Accept' => 'application/json',
|
];
|
}
|
|
protected function setContentTypes(): void
|
{
|
$this->contentTypes = [
|
'recurring' => $this->buildRecurringPaymentFields(),
|
'product' => $this->buildPaymentFields(),
|
'customer' => $this->buildCustomerFields(),
|
'invoice' => $this->buildInvoiceFields(),
|
];
|
}
|
protected function buildRecurringPaymentFields(): array
|
{
|
return [];
|
}
|
protected function buildPaymentFields(): array
|
{
|
return [];
|
}
|
|
protected function buildCustomerFields():array
|
{
|
return [];
|
}
|
|
protected function buildInvoiceFields():array
|
{
|
return [];
|
}
|
|
protected function formatForService(int $itemID, string $type = 'post'): array
|
{
|
return [];
|
// TODO: Implement formatForService() method.
|
}
|
|
public function initializeCheckout():void
|
{
|
|
}
|
/*************************************************************************
|
* REQUESTS
|
*************************************************************************/
|
|
protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array
|
{
|
if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
|
return $this->response(false, 'Rate limit exceeded');
|
}
|
return match ($endpoint) {
|
'connection-test' => $this->response(true,'Connection successful', $response),
|
default => $this->response(false, 'Unhandled response for '.$endpoint),
|
};
|
}
|
protected function performConnectionTest(): bool
|
{
|
$response = $this->getRequest(
|
'connection-test'
|
);
|
return $response['success']??false;
|
}
|
/*************************************************************************
|
* UTILITY
|
*************************************************************************/
|
protected function getApiToken():string|false
|
{
|
return $this->loadCredentials()['api_token']??false;
|
}
|
|
protected function getProductTypes():array
|
{
|
error_log('Product types: '.print_r(explode(',', $this->loadCredentials()['product_types']), true));
|
return explode(',', $this->loadCredentials()['product_types'])??[];
|
}
|
protected function hasProducts():bool
|
{
|
return in_array('product', $this->getProductTypes());
|
}
|
protected function hasServices():bool
|
{
|
return in_array('service', $this->getProductTypes());
|
}
|
protected function hasSubscriptions():bool
|
{
|
return in_array('subscription', $this->getProductTypes());
|
}
|
protected function hasAddOns():bool
|
{
|
return in_array('addon', $this->getProductTypes());
|
}
|
protected function hasQuotes():bool
|
{
|
return in_array('quote', $this->getProductTypes());
|
}
|
|
/*************************************************************************
|
* POST TYPES
|
*************************************************************************/
|
public function registerPostTypes():void
|
{
|
$this->addMenuPages();
|
if (empty(Registrar::withIntegration($this->service_name, 'user'))) {
|
$this->createUserRole();
|
}
|
$this->createInvoices();
|
$this->createProducts();
|
$this->createServices();
|
$this->createAddOns();
|
$this->createPaymentPlan();
|
$this->createSubscriptions();
|
$this->createQuotes();
|
}
|
protected function addMenuPages():void
|
{
|
add_menu_page(
|
'Settings',
|
'Helcim',
|
'edit_posts',
|
'helcim',
|
[$this, 'renderDashboard'],
|
IconsManager::for()->getCSSIcon($this->icon),
|
5
|
);
|
|
$subPages = [
|
'order',
|
];
|
if ($this->hasProducts()) {
|
$subPages[] = 'product';
|
}
|
if ($this->hasServices()) {
|
$subPages[] = 'service';
|
}
|
if ($this->hasAddOns()) {
|
$subPages[] = 'addon';
|
}
|
if ($this->hasSubscriptions()) {
|
$subPages[] = 'subscription';
|
$subPages[] = 'paymentPlan';
|
}
|
if ($this->hasQuotes()) {
|
$subPages[] = 'quote';
|
}
|
|
|
foreach ($subPages as $p) {
|
$label = $p.'Label';
|
$postType = $p.'PostType';
|
$postType = BASE.$this->$postType;
|
$labels = $this->$label;
|
|
add_submenu_page(
|
'helcim',
|
'',
|
'<div class="submenu-sep"></div>',
|
'edit_posts',
|
'sep-'.$p,
|
'__return_false',
|
);
|
add_submenu_page(
|
'helcim',
|
'Create '.$labels[0],
|
'Create '.$labels[0],
|
'edit_posts',
|
'post-new.php?post_type='.$postType,
|
);
|
add_submenu_page(
|
'helcim',
|
$labels[1],
|
$labels[1],
|
'edit_posts',
|
'edit.php?post_type='.$postType,
|
);
|
|
}
|
}
|
protected function createUserRole():void
|
{
|
$client = Registrar::forUser('client', 'Client', 'Clients');
|
$client->setIntegration($this->service_name);
|
$client->setAll(['system']);
|
}
|
|
protected function createInvoices():void
|
{
|
$invoice = Registrar::forPost($this->orderPostType, $this->orderLabel[0],$this->orderLabel[1]);
|
$invoice->setIntegration($this->service_name);
|
$invoice->setIcon('receipt');
|
$invoice->make([
|
'show_in_menu' => false,
|
// 'public' => false,
|
// 'show_admin' => true
|
]);
|
$invoice->setAll(['system']);
|
}
|
|
protected function billingAddress():array
|
{
|
return [
|
'type' => 'group',
|
'label' => 'Billing Address',
|
'fields' => [
|
'name' => [
|
'type' => 'text',
|
'label' => 'Name',
|
],
|
'address' => [
|
'type' => 'location',
|
'label' => 'Address'
|
],
|
'phone' => [
|
'type' => 'phone',
|
'label' => 'Phone',
|
'default' => 'cellPhone'
|
],
|
'email' => [
|
'type' => 'email',
|
'label' => 'Email',
|
'default' => 'user_email'
|
]
|
]
|
];
|
}
|
protected function createProducts():void
|
{
|
$products = Registrar::forPost($this->productPostType, $this->productLabel[0], $this->productLabel[1]);
|
$products->setIntegration($this->service_name);
|
$products->setIcon('shopping-cart-simple');
|
$products->make([
|
|
'show_in_menu' => false,
|
]);
|
$products->setAll(['system']);
|
}
|
protected function createServices():void
|
{
|
$services = Registrar::forPost($this->servicePostType, $this->serviceLabel[0], $this->serviceLabel[1]);
|
$services->setIcon('headset');
|
$services->setIntegration($this->service_name);
|
$services->make([
|
|
'show_in_menu' => false,
|
]);
|
$services->setAll(['system']);
|
}
|
|
protected function createAddOns():void
|
{
|
$addOns = Registrar::forPost($this->addOnsPostType, $this->addOnsLabel[0], $this->addOnsLabel[1]);
|
$addOns->setIntegration($this->service_name);
|
$addOns->setIcon('stack-plus');
|
$addOns->make([
|
|
'show_in_menu' => false,
|
// 'public' => false,
|
// 'show_admin' => true
|
]);
|
$addOns->setAll(['system']);
|
}
|
protected function createPaymentPlan():void
|
{
|
|
$subscription = Registrar::forPost($this->paymentPlanPostType, $this->paymentPlanLabel[0], $this->paymentPlanLabel[1]);
|
$subscription->setIntegration($this->service_name);
|
$subscription->setIcon('arrows-clockwise');
|
$subscription->make([
|
|
'show_in_menu' => false,
|
// 'public' => false,
|
// 'show_admin' => true
|
]);
|
$subscription->setAll(['system']);
|
}
|
|
protected function createSubscriptions():void
|
{
|
$subscription = Registrar::forPost($this->subscriptionPostType, $this->subscriptionLabel[0], $this->subscriptionLabel[1]);
|
$subscription->setIntegration($this->service_name);
|
$subscription->setIcon('user-switch');
|
$subscription->make([
|
|
'show_in_menu' => false,
|
// 'public' => false,
|
// 'show_admin' => true
|
]);
|
$subscription->setAll(['system']);
|
}
|
|
protected function createQuotes():void
|
{
|
$quotes = Registrar::forPost($this->quotePostType, $this->quoteLabel[0], $this->quoteLabel[1]);
|
$quotes->setIntegration($this->service_name);
|
$quotes->setIcon('export');
|
$quotes->make([
|
|
'show_in_menu' => false,
|
// 'public' => false,
|
// 'show_admin' => true
|
]);
|
$quotes->setAll(['system']);
|
}
|
|
public function registerFields():void
|
{
|
if (empty(Registrar::withIntegration($this->service_name, 'user'))) {
|
$this->defineUserFields();
|
}
|
$this->defineInvoiceFields();
|
$this->defineProductFields();
|
$this->defineServiceFields();
|
$this->defineAddOnFields();
|
$this->definePaymentPlanFields();
|
$this->defineSubscriptionFields();
|
}
|
protected function defineUserFields():void
|
{
|
$client = Registrar::getInstance('client');
|
$fields = $client->fields();
|
$fields->addField('contactName', [
|
'type' => 'text',
|
'label' => 'Full Name'
|
]);
|
$fields->addField('businessName', [
|
'type' => 'text',
|
'label' => 'Business Name'
|
]);
|
$fields->addField('cellPhone', [
|
'type' => 'phone',
|
'label' => 'Cell Phone'
|
]);
|
|
$fields->addField('billingAddress', $this->billingAddress());
|
|
$fields->addField('sameAsBilling',[
|
'type' => 'true_false',
|
'label' => 'Shipping Address is same as Billing Address',
|
]);
|
$fields->addField('shippingAddress', [
|
'type' => 'group',
|
'label' => 'Shipping Address',
|
'fields' => [
|
'name' => [
|
'type' => 'text',
|
'label' => 'Name',
|
],
|
'address' => [
|
'type' => 'location',
|
'label' => 'Address'
|
],
|
'phone' => [
|
'type' => 'phone',
|
'label' => 'Phone',
|
'default' => 'cellPhone'
|
],
|
'email' => [
|
'type' => 'email',
|
'label' => 'Email',
|
'default' => 'user_email'
|
]
|
],
|
'condition' => [
|
'field' => 'sameAsBilling',
|
'operator' => '=',
|
'value' => '0'
|
]
|
]);
|
|
$fields->addField('savedCards', [
|
'type' => 'repeater',
|
'label' => 'Saved Cards',
|
'fields' => [
|
'cardId' => [
|
'type' => 'text',
|
'subtype' => 'password',
|
'label' => 'Card ID',
|
'hidden' => true
|
],
|
'cardToken' => [
|
'type' => 'text',
|
'label' => 'Card Token',
|
'hidden'=> true
|
],
|
'dateCreated' => [
|
'type' => 'date',
|
'label' => 'Date Created',
|
],
|
'dateUpdated' => [
|
'type' => 'date',
|
'label' => 'Date Updated',
|
],
|
'cardHolderName' => [
|
'type' => 'text',
|
'label' => 'Card Holder Name',
|
],
|
'cardF6L4' => [
|
'type' => 'text',
|
'label' => 'First 6 and last 4 digits of card',
|
],
|
'cardExpiry' => [
|
'type' => 'date',
|
'label' => 'Card Expiry Date',
|
],
|
'cardCVV' => [
|
'type' => 'text',
|
'label' => 'Card CVV',
|
]
|
]
|
]);
|
}
|
protected function defineInvoiceFields():void
|
{
|
$invoice = Registrar::getInstance($this->orderPostType);
|
$fields = $invoice->fields();
|
|
$fields->addField('currency',[
|
'type' => 'radio',
|
'label' => 'Currency',
|
'options' => [
|
'CAD' => 'CAD',
|
'USD' => 'USD'
|
],
|
'default' => 'CAD'
|
]);
|
|
$fields->addField('lineItems', [
|
'type' => 'repeater',
|
'label' => 'Line Items',
|
'fields' => [
|
'makeCustom' => [
|
'type' => 'true_false',
|
'label' => 'Make Custom',
|
],
|
'item' => [
|
'type' => 'selector',
|
'subtype' => 'post',
|
'post_type' => [$this->productPostType, $this->servicePostType, $this->paymentPlanPostType, $this->addOnsPostType],
|
'label' => 'Select Item',
|
'condition' => [
|
'field' => 'makeCustom',
|
'operator' => '!=',
|
'value' => '1'
|
],
|
'max' => 1,
|
],
|
'description' => [
|
'type' => 'text',
|
'label' => 'Item name',
|
'condition' => [
|
'field' => 'makeCustom',
|
'operator' => '=',
|
'value' => '1'
|
]
|
],
|
'quantity' => [
|
'type' => 'number',
|
'label' => 'Quantity',
|
],
|
'price' => [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Price',
|
'condition' => [
|
'field' => 'makeCustom',
|
'operator' => '=',
|
'value' => '1'
|
],
|
'hint' => 'The unit price for a single quantity of this line item, before taxes or discounts.'
|
],
|
'taxAmount' => [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Tax Amount',
|
'condition' => [
|
'field' => 'makeCustom',
|
'operator' => '=',
|
'value' => '1'
|
],
|
'hint' => 'The tax amount for the item',
|
],
|
'discountAmount' => [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Discount',
|
'hint' => 'The discount applied to the item',
|
'condition' => [
|
'field' => 'makeCustom',
|
'operator' => '=',
|
'value' => '1'
|
],
|
],
|
'sku' => [
|
'type' => 'text',
|
'label' => 'Stock Keeping Unit'
|
]
|
]
|
]);
|
|
$fields->addField('customer', [
|
'type' => 'selector',
|
'label' => 'For Customer',
|
'subtype' => 'user'
|
]);
|
|
$fields->addField('billingAddress', $this->billingAddress());
|
|
$fields->addField('shipping', [
|
'type' => 'group',
|
'label' => 'Shipping Information',
|
'fields' => [
|
'amount' => [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Amount',
|
'hint' => 'The total shipping amount for the invoice.'
|
],
|
'details' => [
|
'type' => 'text',
|
'label' => 'Short description of shipping service',
|
'hint' => 'A description of the shipping method. This information appears on the invoice.
|
Ex: "Canada Post 1-Day"'
|
],
|
'address' => $this->billingAddress()
|
]
|
]);
|
|
$fields->addField('pickupDate', [
|
'type' => 'date',
|
'label' => 'Pickup Date',
|
]);
|
|
$fields->addField('tax', [
|
'type' => 'group',
|
'label' => 'Tax',
|
'fields' => [
|
'amount' => [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Amount',
|
'hint' => 'The total tax amount applied at the invoice level. This does not include item-level taxes'
|
],
|
'details' => [
|
'type' => 'text',
|
'label' => 'Description',
|
'hint' => 'A description of the tax or tax breakdown.',
|
'default' => 'GST 5%'
|
]
|
]
|
]);
|
$fields->addField('discount', [
|
'type' => 'group',
|
'label' => 'Discount',
|
'fields' => [
|
'amount' => [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Amount',
|
'hint' => 'The total invoice applied at the invoice level. This does not include item-level discounts'
|
],
|
'details' => [
|
'type' => 'text',
|
'label' => 'Description',
|
'hint' => 'A description or code identifying the discount.',
|
]
|
]
|
]);
|
|
$fields->addField('type', [
|
'type' => 'select',
|
'label' => 'Type',
|
'options' => [
|
'ESTIMATE' => 'Estimate',
|
'QUOTE' => 'Quote',
|
'ORDER' => 'Order',
|
'PURCHASE_ORDER' => 'Purchase Order',
|
'INVOICE' => 'Invoice',
|
'STATEMENT' => 'Statement',
|
'REGISTRATION' => 'Registration',
|
'CREDIT' => 'Credit',
|
],
|
'default' => 'INVOICE',
|
]);
|
|
$fields->addField('status', [
|
'type' => 'select',
|
'label' => 'Status',
|
'default' => 'DUE',
|
'options' => [
|
'CANCELLED' => 'Cancelled',
|
'DUE' => 'Due',
|
'PAID' => 'Paid',
|
'SHIPPED' => 'Shipped',
|
'COMPLETED' => 'Completed',
|
'REFUNDED' => 'Refunded',
|
'IN_PROGRESS'=> 'In Progress',
|
]
|
]);
|
|
$fields->addField('convenienceFeeEnabled', [
|
'type' => 'true_false',
|
'label' => 'Convenience Fee Enabled',
|
'hint' => 'Indicates whether a convenience fee is enabled for this invoice. If not provided, the system will use the merchant\'s global Fee Saver settings to determine this value.'
|
]);
|
|
$fields->addField('tipAmount', [
|
'type' => 'text',
|
'label' => 'Tip Amount',
|
'subtype' => 'number',
|
'hint' => 'Optional tip amount to include. Must be greater than zero if provided.'
|
]);
|
|
$fields->addField('notes', [
|
'type' => 'textarea',
|
'label' => 'Notes',
|
'hint' => 'Optional notes to display at the bottom of the invoice, visible to the customer.'
|
]);
|
}
|
protected function defineAddOnFields():void
|
{
|
$addOns = Registrar::getInstance($this->addOnsPostType);
|
$fields = $addOns->fields();
|
|
$fields->modifyField('post_title', 'maxLength', 256);
|
$fields->modifyField('post_title', 'hint', 'The new add-on name. All add-ons must have a name between 3 and 256 characters in length.');
|
|
$fields->addField('amount', [
|
'required' => true,
|
'label' => 'Amount',
|
'type' => 'text',
|
'subtype' => 'number',
|
'hint' => 'The amount charged to subscriptions which apply this add-on. This amount is charged for each copy of
|
the add-on applied. For example an add-on with a quantity of three will charge this amount three times for
|
the given subscription recurring billing.'
|
]);
|
|
$fields->addField('frequency', [
|
'type' => 'select',
|
'label' => 'Frequency',
|
'required' => true,
|
'options' => [
|
'one_time' => 'Add-on is charged only during the next upcoming recurring billing',
|
'recurring' => 'Add-on is charged for every recurring billing after date of application'
|
]
|
]);
|
|
$fields->addField('isQuantityEditable', [
|
'label' => 'Is Quantity Editable',
|
'type' => 'true_false',
|
'hint' => 'Specifies whether the add-on can be applied multiple times to the same subscription or be edited to change
|
the number of applications for a linked subscription. Note this toggle only affects add-ons currently applied
|
or being applied and not add-ons previously linked in the past. Such add-ons can still be
|
reapplied after their removal.'
|
]);
|
}
|
protected function defineProductFields():void
|
{
|
|
}
|
protected function defineServiceFields():void
|
{
|
|
}
|
protected function definePaymentPlanFields():void
|
{
|
$type = Registrar::getInstance($this->paymentPlanPostType);
|
|
$fields = $type->fields();
|
|
$fields->modifyField('post_title', 'maxLength', 100);
|
$fields->modifyField('post_title', 'hint', 'The new payment plan name. All plans must have a name between 3 and 100 characters in length.');
|
|
$fields->addField('description', [
|
'type' => 'textarea',
|
'label' => 'Description',
|
'maxLength' => 250,
|
'hint' => 'Additional information describing the new payment plan.'
|
]);
|
|
$fields->addField('type', [
|
'type' => 'select',
|
'label' => 'Type',
|
'options' => [
|
'subscription' => 'Subscription',
|
'cycle' => 'Cycle',
|
],
|
'hint' => 'The payment plan billing type:
|
- subscription - On sign-up plan bills on subscription activation date
|
- cycle - Pick a day plans bill on specific days',
|
'required' => true,
|
]);
|
|
$fields->addField('status', [
|
'type' => 'select',
|
'label' => 'Status',
|
'options' => [
|
'active' => 'Active Plan',
|
'inactive' => 'Inactive Plan',
|
]
|
]);
|
|
$fields->addField('setupAmount', [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Setup Amount',
|
'hint' => 'The initial monetary amount charged for new subscriptions to the payment plan.
|
Can be charged immediately or with the first recurring billing.'
|
]);
|
|
$fields->addField('recurringAmount', [
|
'type' => 'text',
|
'subtype' => 'number',
|
'label' => 'Recurring Amount',
|
'required' => true,
|
'hint' => 'The recurring monetary amount charged for subscriptions to the payment plan. Note a plan can have a recurring
|
amount of zero, however all subscriptions to the plan must then individually specify
|
their charged recurring amount.'
|
]);
|
|
$fields->addField('billSetupImmediately', [
|
'type' => 'select',
|
'label' => 'First charge',
|
'options' => [
|
'first_billing' => 'with first recurring payment',
|
'immediate' => 'immediately'
|
],
|
'hint' => 'Toggle indicating whether the payment plan setup amount should be charged to subscriptions at time of sign-up'
|
]);
|
|
$fields->addField('billingPeriod', [
|
'type' => 'select',
|
'label' => 'Billing Period',
|
'hint' => 'The cycle at which subscriptions to the payment plan are billed:',
|
'options' => [
|
'daily' => 'Cycle value for plans billed daily',
|
'weekly' => 'Cycle value for plans billed weekly',
|
'monthly' => 'Cycle value for plans billed monthly',
|
'yearly' => 'Cycle value for plans billed yearly',
|
],
|
'required' => true,
|
]);
|
|
$fields->addField('billingPeriodIncrements', [
|
'type' => 'number',
|
'label' => 'Frequency',
|
'required' => true,
|
'hint' => 'The frequency to which subscriptions for the payment plan are processed based on billing period.'
|
]);
|
|
$fields->addField('dateBilling', [
|
'type' => 'text',
|
'label' => 'Bill Date',
|
'required' => true,
|
'hint' => 'The date subscriptions to the payment plan are billed. This field requires specific formatting depending on the plan\'s billing cycle. See below examples for required formats:',
|
'description' => [
|
'"Sign-up" - Stored billing date for sign-up plans',
|
'"Daily" - Stored billing date for billing daily',
|
'"Monday" - Weekly billing plans billed on a specific day store their billing day as the weekday name',
|
'"08" - Monthly billing plans billing on a specific day store their billing day in DD format',
|
'"06-15" - Yearly billing plans billing on a specific day store their billing day in MM-DD format'
|
]
|
]);
|
|
$fields->addField('termType', [
|
'type' => 'select',
|
'label' => 'Term Type',
|
'required' => true,
|
'options' => [
|
'forever' => 'Does not expire',
|
'expires' => 'Expires after a set number of billings',
|
],
|
'hint' => 'Identifies whether subscriptions to the plan expire after a set number of billings'
|
]);
|
|
$fields->addField('freeTrialPeriod', [
|
'type' => 'number',
|
'label' => 'Free Trial Period',
|
'hint' => 'The free trial length (in days) subscriptions to the payment plan can have'
|
]);
|
|
$fields->addField('taxType', [
|
'type' => 'select',
|
'label' => 'Tax Rate',
|
'hint' => 'The tax rate applied to subscriptions to the payment plan. Not specifying a value will default to not applying taxes on subscriptions to the payment plan',
|
'options' => [
|
'no_tax' => 'No taxes applied to subscriptions to the plan',
|
'customer' => 'Subscriptions to the plan tax according to the customer\'s location',
|
'merchant' => 'Subscriptions to the plan tax according to your business\' location'
|
],
|
'default' => 'no_tax',
|
]);
|
|
$fields->addField('taxCalculation', [
|
'type' => 'select',
|
'label' => 'Tax Calculation',
|
'hint' => 'The specific country and province/state tax(es) applied to the tax rate calculation. Not specifying a value will default to country_province. Note that taxes will only be applied to subscriptions to the plan if taxType is not',
|
'options' => [
|
'country_only' => 'country tax applied only',
|
'country_province' => 'summed total of country and province/state taxes applied',
|
'province_only' => 'province/state tax is applied only'
|
]
|
]);
|
|
$fields->addField('termLength', [
|
'type' => 'number',
|
'label' => 'Term Length',
|
'hint' => 'The maximum number of cycles expiring subscriptions to this payment plan can bill for. Note this value
|
only provides a default; subscriptions can separately define a maximum cycle length if desired.
|
Defining this value requires the payment plan\'s termType to to be expires.'
|
]);
|
|
$fields->addField('paymentMethod', [
|
'required' => true,
|
'label' => 'Payment Method',
|
'type' => 'select',
|
'hint' => 'The allowed payment methods subscriptions to this plan can utilize',
|
'options' => [
|
'card' => 'Subscriptions to plan bill via credit card',
|
'bank' => 'Subscriptions to the plan bill via ACH',
|
'card_bank' => 'Subscriptions to the plan can bill via either credit card or ACH'
|
]
|
]);
|
|
$fields->addField('addOnIds', [
|
'type' => 'selector',
|
'label' => 'Add On IDs',
|
'subtype' => 'post',
|
'post_type' => $this->addOnsPostType,
|
'hint' => 'List of add-ons to link to this payment plan and allow subscriptions to apply'
|
]);
|
|
$fields->addField('emailSettingRecurringQueue', [
|
'type' => 'select',
|
'label' => 'Recurring Emails',
|
'default' => 'off',
|
'options' => [
|
'off' => 'No Emails',
|
'merchant' => 'Send recurring queue emails to the merchant'
|
],
|
'hint' => 'Whether recurring queue emails are sent to the merchant.'
|
]);
|
|
$fields->addField('businessEmail', [
|
'type' => 'email',
|
'label' => 'Business Email',
|
'hint' => 'Merchant email used for subscriptions emails. Must not be empty if emailSettingRecurringQueue is merchant.'
|
]);
|
|
$fields->addField('isProrated', [
|
'type' => 'select',
|
'label' => 'Is Prorated',
|
'options' => ['yes', 'no'],
|
'hint' => 'Identifies whether or not a payment plan is prorated. Subscriptions to prorated plans will charge a prorated first payment on the activation date or after the free trial period has ended. Note that prorated amounts include the recurring amount and recurring add-ons amount. Setup amounts and one-time add-ons are not prorated. Defining this value requires the the payment plan\'s type to be cycle.'
|
]);
|
}
|
|
protected function defineSubscriptionFields():void
|
{
|
$subscription = Registrar::getInstance($this->subscriptionPostType);
|
$fields = $subscription->fields();
|
|
|
}
|
|
public function addDashboardPages():void
|
{
|
$page = Dashboard::getInstance()->addPage('Your Orders', 'orders', 'receipt');
|
$page->setScripts(['jvb-crud']);
|
}
|
public function renderDashPage():void
|
{
|
$this->determineUser();
|
|
$crud = new CRUDSkeleton();
|
$crud->icon('receipt');
|
$crud->title('Your Orders', 'Here you can see your past orders and reorder if you\'d like.');
|
$crud->content($this->orderPostType, $this->orderLabel[0], $this->orderLabel[1]);
|
$crud->addSearch();
|
$crud->addCapabilities(['view']);
|
$crud->setEmptyState(sprintf(
|
'<div class="empty-state">
|
<h3>%sNothing here %s</h3>
|
<p>It doesn\'t look like you have any orders yet.</p>
|
<p>Head on over to <a href="%s">our shop page</a> and make your first order!</p>
|
</div>',
|
jvbDashIcon($crud->getIcon()),
|
jvbDashIcon($crud->getIcon()),
|
get_post_type_archive_link(BASE.$this->productPostType),
|
));
|
|
$crud->render();
|
}
|
|
/**************************************************************************
|
* WEBHOOKS
|
**************************************************************************/
|
protected function setWebhookHandle():void
|
{
|
$this->webhookHandle = 'hcm42';
|
}
|
protected function validateWebhook(array $payload): bool
|
{
|
|
}
|
|
protected function processWebhook(array $payload): bool
|
{
|
// TODO: Implement processWebhook() method.
|
}
|
|
protected function extractWebhookId(array $payload): string
|
{
|
// TODO: Implement extractWebhookId() method.
|
}
|
|
public function renderDashboard():void
|
{
|
echo '<h1>Helcim Settings</h1>';
|
|
$this->renderHelcimOptions();
|
}
|
protected function renderHelcimOptions():void
|
{
|
|
|
$buttons = [
|
'import_products',
|
'import_subscriptions',
|
'import_addons',
|
'import_orders',
|
];
|
}
|
|
/********************************************************************
|
* ACTIONS
|
********************************************************************/
|
public function importFromHelcim():void
|
{
|
$this->importOrders();
|
$this->importAddons();
|
$this->importSubscriptions();
|
$this->importActiveSubscriptions();
|
}
|
public function importOrders():void
|
{
|
|
}
|
public function importAddons():void
|
{
|
|
}
|
public function importSubscriptions():void
|
{
|
|
}
|
public function importActiveSubscriptions():void
|
{
|
|
}
|
|
public function syncToHelcim():void
|
{
|
$this->syncOrders();
|
$this->syncAddOns();
|
$this->syncSubscriptions();
|
$this->syncActiveSubscriptions();
|
}
|
public function syncOrders():void
|
{
|
|
}
|
public function syncAddOns():void
|
{
|
|
}
|
public function syncSubscriptions():void
|
{
|
|
}
|
public function syncActiveSubscriptions():void
|
{
|
|
}
|
}
|