Real Time shipping - custom method

Hi,

I am developing my first addon for a real time shipping calculation. MVE 4.18.3 standard.

The structure i have is below and i have the addon installed and active.

senetic_shipping_live/
├── addon.xml
├── config.php
├── func.php
├── init.php
├── Tygh/
│ └── Shippings/
│ └── Services/
│ └── Senetic.php
├── schema/
│ └── shippings/
│ └── services.php

I am not seeing the addon in the realtime shipping menu to select it.

If anyone has any info or tips on this approach that would be great.

the Senetic.php file contains an api call to a supplier who returns the shipping costs. i just cant get the addon to show in the realtime menu to select it and see if the logic in the php is working.

if i add into the DB manually it shows so its like the schema isnt getting triggered.

INSERT INTO cscart_shipping_services( status, module, code, sp_file, description, rate_calculation, position) VALUES ( 'A', 'senetic_shipping_live', 'senetic', 'Senetic', 'Senetic Live Shipping', 'R', 100 );

Thanks

Hello

You should add entries in these tables:

?:shipping_services
?:shipping_service_descriptions
?:shippings
?:shipping_descriptions

also for Store Bulider Ultimate
?:ult_objects_sharing

and file should be in location:
Tygh/Shippings/Services/Senetic.php

Best regards
Robert

Hi Robert,

thanks for your reply. i can add them manually but the Senetic.php file isnt triggered. I would of thought the addon would do this on installation automatically.

the init.php

<?php

if (!defined('BOOTSTRAP')) { die('Access denied'); }

fn_register_hooks('shippings_get_services_post');

//file_put_contents(__DIR__ . '/init_debug.txt', "✅ init.php ran\n", FILE_APPEND);

i can see the debug init file generated

func.php i dont see the hook executed

<?php
if (!defined('BOOTSTRAP')) { die('Access denied'); }

//file_put_contents(__DIR__ . '/func_check.txt', "✅ func.php loaded\n", FILE_APPEND);

function fn_senetic_shipping_live_shippings_get_services_post(&$services)
{
    file_put_contents(__DIR__ . '/hook_debug_fn.txt', "✅ Hook executed\n", FILE_APPEND);

    $services['senetic'] = [
        'name'             => 'senetic',
        'description'      => 'Senetic Live Shipping',
        'rate_calculation' => 'r',
        'service_code'     => 'senetic',
        'sp_file'          => 'Senetic',
        'dependencies'     => ['zipcode', 'country', 'weight', 'subtotal'],
    ];
}

services.php i dont see the scheme_debug.txt file been generated as a debug method to see if its triggered.

<?php

file_put_contents(fn_get_files_dir_path() . 'senetic_schema_debug.txt', "✅ schema loaded\n", FILE_APPEND);

return [
    'senetic' => [ // This key should match 'service_code' and the class name (Senetic)
        'name'             => __('senetic'), 
        'description'      => __('senetic_live_shipping_description'), 
        'rate_calculation' => 'R',
        'service_code'     => 'senetic',
        'sp_file'          => 'Senetic',
        'status'           => 'A',
        'display_info'     => [
            'name' => __('senetic'),
        ],
        'settings'         => [ 
            'auth_token' => [
                'title'       => __('senetic_auth_token'),
                'type'        => 'input',
                'default'     => '',
                'tooltip'     => __('senetic_auth_token_tooltip'),
                'required'    => true,
            ],
            // You can add more settings here if you want to make other parameters
            // of your request body configurable (e.g., isTesting, default payment method)
        ],
        'dependencies'     => ['zipcode', 'country', 'weight', 'subtotal'], 
        'tracking_url'     => '',
    ]
];

Senetic.php in Tygh folder of the addon

<?php

namespace Tygh\Shippings\Services;

use Tygh\Shippings\IService;
use Tygh\Shippings\Service;
use Tygh\Http;
use Tygh\Registry;

class Senetic extends Service implements IService
{
    // Logic
}

I would prefer if the addon installation done all the inserts into the DB automatically.

If anyone can see anything wrong with my approach please let me know.

Thanks

Hello

Notice that your “Senetic” class implements the “IService” interface, so according to the standards, the body of this class should contain the methods required by the interface. Are they there and you didn’t show them or are they missing?

Best regards
Robert

No this should be done by the code in your add-on.

In the addon.xml add these elements:

    <functions>
        <item for="install">fn_my_addon_install</item>
        <item for="uninstall">fn_my_addon_uninstall</item>
    </functions>

and in the func.php add these functions, that will be called during installation / uninstallation.

function fn_my_addon_install()
{
    ...
    $new_service_id = db_query('INSERT INTO ?:shipping_services ?e', $new_service);
    $new_service_description = [
        'service_id' => $new_service_id,
        'description' => $description
    ];
    foreach (Languages::getAll() as $new_service_description['lang_code'] => $lang_data) {
        db_query('INSERT INTO ?:shipping_service_descriptions ?e', $new_service_description);
    }
}

function fn_my_addon_uninstall(): void
{
    $ids_to_delete = db_get_fields('SELECT service_id FROM ?:shipping_services WHERE module = ?s', 'my_addon');
    db_query('DELETE FROM ?:shipping_service_descriptions WHERE service_id IN (?n)', $ids_to_delete);
    db_query('DELETE FROM ?:shipping_services WHERE service_id IN(?n)', $ids_to_delete);
}

Hi Robert,

Yes they are there , just cant get this to fire . I am trying to test the api call and if that doesnt work fall back on a value so i can get somethign to show on the front end , no joy yet,

<?php

namespace Tygh\Shippings\Services;

use Tygh\Shippings\IService;
use Tygh\Shippings\Service;
use Tygh\Http;
use Tygh\Registry;

class Senetic extends Service implements IService
{
    protected $settings = [];
    protected $_shipping_info = [];

    public function prepareData($shipping_info)
    {
        $this->_shipping_info = $shipping_info;
        $this->settings = $shipping_info['service_params'];
    }

    public function getRequestData(): array
    {
        $url = 'https://b2b.senetic.com/Gateway/ClientApi/OrderCreate';

        $token = Registry::get('config.senetic_api_token');

        return [
            'method' => Http::POST,
            'url'    => $url,
            'data'   => json_encode($this->buildRequestBody()),
            'headers' => [
                'Content-Type: application/json',
                'Authorization: Basic ' . base64_encode($token),
            ],
        ];
    }

    public function getSimpleRates(): array
    {
        $data = $this->getRequestData();

        // Optional debug log
        file_put_contents(fn_get_files_dir_path() . 'senetic_request_debug.txt', print_r($data, true), FILE_APPEND);

        $response = Http::post($data['url'], $data['data'], ['headers' => $data['headers']]);

        // Log API response
        file_put_contents(fn_get_files_dir_path() . 'senetic_api_response.txt', $response, FILE_APPEND);

        return $this->processResponse($response);
    }

    public function processResponse($response): array
    {
        $rate = [
            'cost' => false,
            'error' => false,
            'delivery_time' => '5-7 days',
        ];
    
        $log_file = fn_get_files_dir_path() . 'senetic_process_log.txt';
    
        file_put_contents($log_file, "\n\n===== New API Call =====\n", FILE_APPEND);
        file_put_contents($log_file, "RAW Response:\n" . print_r($response, true), FILE_APPEND);
    
        $decoded = json_decode($response, true);
        file_put_contents($log_file, "\n\nDecoded Response:\n" . print_r($decoded, true), FILE_APPEND);
    
        if (!empty($decoded['header']['delivery']['deliveryItemsCarrierId'])) {
            $rate['cost'] = 9.99; // Later, use $decoded value here
        } else {
            $rate['error'] = 'No shipping options found in API response.';
        }
        
        if (!empty($decoded['header']['delivery']['deliveryItemsCarrierId'])) {
            $rate['cost'] = 9.99; // Replace with actual value later
        } else {
            $rate['error'] = 'No shipping options found in API response.';

            // Fallback to default rate for testing
            $rate['cost'] = 9.99;
            $rate['error'] = false; // Clear the error so CS-Cart shows the method
        }

    
        file_put_contents($log_file, "\n\nFinal Rate Returned:\n" . print_r($rate, true), FILE_APPEND);
    
        return $rate;
    }


    public function allowMultithreading()
    {
        return false;
    }

    private function buildRequestBody(): array
    {
        return [
            'header' => [
                'orderCurrency' => 'EUR',
                'clientOrderNumber' => uniqid('TEST_'),
                'delivery' => [
                    'dropshipping' => true,
                    'deliveryAddress' => [
                        'name' => 'Cart User',
                        'addressLine' => 'Main St',
                        'city' => 'Dublin',
                        'postalCode' => 'D01',
                        'country' => 'IE',
                        'phone' => '00000000',
                        'email' => 'test@example.com',
                    ],
                ],
                'payment' => [
                    'paymentMethodId' => 'INVOICE'
                ],
                'endUserOrder' => 'CCart'
            ],
            'lines' => [
                [
                    'itemId' => 'test_item',
                    'quantity' => 1,
                    'manualNetPrice' => 0
                ]
            ],
            'isTesting' => true
        ];
    }

    public static function getInfo(): array
    {
        return [
            'name' => __('senetic'),
            'tracking_url' => '',
        ];
    }
}

Thanks folks , i managed to get the option to load into the menu now and i have it allocated to a supplier ( yes i use the supplier module !) . when i do a test calculation or visit the cart i cant get it to call the api or show a programmed default value.

does the Senetic.php file belong in the addon Tygh directory or the main app/Tygh directory ?

thanks

1 Like

Hello

Name space should be “Tygh\Shippings\Services”
As you write an addon it is good to implement a logger so that you can check if something is being called even if it is done by Ajax. Have you checked if your object’s methods are being called? The ones responsible for getting data?

Best regards
Robert

1 Like

As I can see from your add-on’s file structure scheme, the Senetic.php file is located in the Tygh folder, however it should be located inside the Tygh/Shippings/ folder inside your add-on.

Hi Robert,

I thought i had the namespace named correctly ? am i missing something can you expand ?

HI,

Sorry the chart represents it bad. I have the Senetic.php file inside Tygh/Shippings/Services in my addon folder

1 Like

Hello

Your file location is correct.
I ask again, are the functions from your object being called?
Have you checked this?
Also show the database entries for this delivery

Best regards
Robert

Hi Robert,

Thanks for confirming the structure.Sorry for not replying on this point.

i have put file logging in the functions of Senetic.php at various points and i have yet to see any logging from the main Senetic.php file.

example below to see if the class is loaded. I have similar in func and init php files where i see they files been called.


class Senetic extends Service implements IService
{
    protected $settings = [];
    protected $_shipping_info = [];

    public function __construct()
    {
        file_put_contents(fn_get_files_dir_path() . 'senetic_class_loaded.txt', "✅ Senetic class loaded\n", FILE_APPEND);
    }

    // rest of logic

example of another function but i havent saw the log txt file yet

    public function getRequestData(): array
    {
        $url = 'https://b2b.senetic.com/Gateway/ClientApi/OrderCreate';

        $token = Registry::get('config.senetic_api_token'); // Already base64 encoded

        $request_data = [
            'method'  => Http::POST,
            'url'     => $url,
            'data'    => json_encode($this->buildRequestBody()),
            'headers' => [
                'Content-Type: application/json',
                'Authorization: Basic ' . $token,
            ],
        ];

        file_put_contents(fn_get_files_dir_path() . 'senetic_request_debug.txt', print_r($request_data, true), FILE_APPEND);

        return $request_data;
    }

so to answer your question i havent seen any logging in this file yet.

thanks

Hello

If you can, contact me in a private message. If you have a copy of the devel system, we can look there and see what the problem is.

Best regards
Robert

1 Like