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 Helcim Dashboard', '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 ''; } 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', '', '
', '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( 'It doesn\'t look like you have any orders yet.
Head on over to our shop page and make your first order!