Skip to content

SMS Integrations (Yuboto, Routee, Plivo, Bulker, Apifon, OmniMessaging)

Flow ID: IN-14 | Module(s): ecommercen/helpers/, src/Yuboto/, src/Bulker/, src/Apifon/, src/Omni/ | Complexity: High

Business Overview

Ecommercen sends SMS (and optionally Viber) notifications to customers for order status updates, primarily when an order is ready for pickup ("from store" status). The platform supports six SMS providers through a unified dispatcher function, allowing each client to select their preferred provider via configuration.

The SMS system handles two distinct use cases:

  1. Order-triggered SMS (sendSms()) -- Sends a predefined order notification message with special Friday/Saturday delivery handling. Records the message in customer communication history.
  2. Generic SMS dispatch (sendSmsMsg()) -- Sends arbitrary SMS/Viber messages through the configured provider. Used by jobs, gift card notifications, and other subsystems.

All providers share a common phone validation and normalization layer optimized for Greek mobile numbers.

API Reference

Unified Dispatcher Functions

FunctionParametersDescription
sendSms($orderSerial, $phone, $provider, $extra)string, string, string, arrayOrder notification SMS with history logging
sendSmsMsg($msg, $viberMsg, $phone, $provider, $extra)string, string, string, string, arrayGeneric SMS/Viber dispatch
isValidPhoneForSms($phone)stringValidates Greek mobile number format
convertToValidPhoneForSms($phone)stringNormalizes to 30xx format
getFirstValidPhoneForSms($phones)arrayReturns first valid phone from array
getFirstValidPhoneForSmsFormatted($phones)arrayReturns first valid phone, normalized
getValidPhoneForPayway($payway, ...)string, stringsReturns valid phone for specific payment methods

Provider-Specific APIs

ProviderClassChannelsFeatures
YubotoLegacy helper functionsSMSCallback URL support
YubotoOmniAdvisable\Yuboto\YubotoOmniSMS + ViberPriority-based channel selection
OmniMessagingAdvisable\Omni\OmniMessagingSMS + ViberTransaction ID tracking, validity periods
RouteeLegacy helper functionsSMS, SMS+ViberDLR callback URL
PlivoLegacy helper functionsSMS onlyBasic send
BulkerAdvisable\Bulker\BulkerSmsSMS onlyStatus polling, delivery reports
ApifonAdvisable\Apifon\ApifonSMS + ViberOAuth2, TTL-based Viber-to-SMS fallback

Code Flow

Order Notification SMS

Order status changes to "from_store"
  -> AdvSendEmailAndSMSBasedOnFromStoreStatus job (or controller)
     -> sendSms($orderSerial, $phone, $provider, $extra)
        -> Determine message based on day of week:
           - Friday: Saturday delivery message (t('sms.order.message.saturday.*'))
           - Other days: Standard message (t('sms.order.message.*'))
        -> Build Viber message variant (t('viber.order.from_store.message*'))
        -> Log to customer_message_history_model (type: ORDER_ON_STORE, channel: SMS)
        -> Route to provider via switch/case

Provider Dispatch Flow

sendSmsMsg($msg, $viberMsg, $phone, $provider, $extra)
  switch ($provider):
    'plivo'         -> load plivo_helper   -> sendSmsPlivo($msg, $phone)
    'routee'        -> load routee_helper  -> sendSmsRoutee($msg, $phone, $callbackUrl)
    'routeeViber'   -> load routee_helper  -> sendViberSmsRoutee($msg, $phone, $callbackUrl)
    'yuboto'        -> load yuboto_helper  -> sendSmsYuboto($msg, $phone, $callbackUrl)
    'yuboto_omni'   -> load yuboto_omni config -> YubotoOmni::sendMessage($msg, $viberMsg, $phone, $callbackUrl)
    'omni_messaging'-> load omni config    -> OmniMessaging::sendMessage($msg, $viberMsg, $phone, $orderId)
    'bulker'        -> load bulker config  -> BulkerSms::sendMessage($msg, $phone, $orderId)
    'apifon'        -> (registry-based)    -> Apifon::sendMessage($phone, $msg, $viberMsg)

YubotoOmni Channel Priority

YubotoOmni::sendMessage($msg, $viberMsg, $phone, $callbackUrl)
  -> Build JSON payload:
     - phonenumbers: $phone
     - dlr: true if callbackUrl provided
  -> If Viber message provided:
     - viber: { sender, text, priority: 0 }  (primary)
  -> If SMS message provided:
     - sms: { sender, text, priority: 1 if Viber exists, else 0 }  (fallback)
  -> POST to {apiBaseUrl}/omni/v1/Send
  -> Auth: Basic (base64-encoded API key)
  -> Check response: ErrorCode == 0, Message[0].status not Error/Failed

OmniMessaging Dual Channel

OmniMessaging::sendMessage($msg, $viberMsg, $phone, $orderId)
  -> Build JSON payload:
     - transaction_id: $orderId
     - channels: [
         { viber: { message: { text }, validity_period: 10 } },
         { sms: { from: sender_name, text, encoding: 0, validity_period: 120 } }
       ]
     - destinations: [{ phone_number }]
  -> POST to {apiBaseUrl}/sendings
  -> Auth: Basic (base64-encoded userID:authKey)

Apifon SMS with Viber Fallback

Apifon::sendMessage($to, $msg, $viberMsg)
  -> authenticate() if not already authenticated (OAuth2 client_credentials)
  -> If enabledIm (Viber enabled):
     - POST to /services/api/v1/im/send
     - Include im_channels with TTL for Viber-to-SMS fallback
  -> Else:
     - POST to /services/api/v1/sms/send
  -> Body: message (text, sender_id), subscribers (number)

Bulker Status Polling

AdvGetBulkerSmsStatus job (cron)
  -> Query orders with sms_status = 4 (buffered)
  -> For each order:
     -> BulkerSms::getSmsStatus($orderId)
        -> GET {apiBaseUrl}/dlr.php?auth_key={key}&id={orderId}
        -> Map response: 1 -> delivered(2), 2 -> undelivered(3), 4 -> buffered(4)
     -> Update shop_order.sms_status

Architecture

ecommercen/helpers/
  sms_helper.php               # Unified dispatcher (sendSms, sendSmsMsg, phone validation)
  yuboto_helper.php            # Yuboto legacy SMS helper
  routee_helper.php            # Routee SMS/Viber helper
  plivo_helper.php             # Plivo SMS helper

src/Yuboto/
  YubotoOmni.php               # YubotoOmni SMS+Viber client (priority-based channels)

src/Omni/
  OmniMessaging.php            # OmniMessaging SMS+Viber client (dual channel)

src/Bulker/
  BulkerSms.php                # Bulker SMS client with delivery status polling

src/Apifon/
  Apifon.php                   # Apifon OAuth2 SMS+Viber client

ecommercen/job/libraries/
  AdvSendEmailAndSMSBasedOnFromStoreStatus.php   # Job: SMS on from_store status
  AdvSendEmailAndSMSBasedOnSentStatus.php        # Job: SMS on sent status
  AdvGetBulkerSmsStatus.php                      # Job: Bulker delivery status polling

ecommercen/gift_cards/jobs/
  GiftCardSendSms.php          # Gift card SMS notification

application/libraries/
  Yuboto.php                   # Client-overridable Yuboto library
  Routee.php                   # Client-overridable Routee library
  Bulker.php                   # Client-overridable Bulker library

application/config/
  yuboto.php                   # Yuboto legacy config
  yuboto_omni.php              # YubotoOmni config
  routee.php                   # Routee config
  plivo.php                    # Plivo config
  bulker.php                   # Bulker config

Provider Authentication

ProviderAuth MethodDetails
Yuboto (legacy)Config-basedAPI key in helper config
YubotoOmniBasic AuthBase64-encoded API key
OmniMessagingBasic AuthBase64-encoded userID:authKey
RouteeConfig-basedVia routee_helper config
PlivoConfig-basedVia plivo_helper config
BulkerQuery parameterauth_key in URL
ApifonOAuth2 Bearerclient_credentials grant, Bearer token

Data Model

Tables

TableColumnDescription
shop_ordersms_statusSMS delivery status: 1=sent, 2=delivered, 3=undelivered, 4=buffered
customer_message_historyVariousCommunication log: customer_id, type (ORDER_ON_STORE), channel (SMS), subject, body

SMS Status Values

ValueMeaningContext
1SentInitial state after successful send
2DeliveredConfirmed delivery (Bulker polling)
3UndeliveredFailed delivery (Bulker polling)
4BufferedIn transit, pending confirmation

Configuration

Provider Config Files

ProviderConfig FileKey Settings
Yubotoapplication/config/yuboto.phpAPI URL, sender name, API key
YubotoOmniapplication/config/yuboto_omni.phpapiBaseUrl, apiKey, smsSender, viberSender, timeout
Routeeapplication/config/routee.phpAPI credentials, sender
Plivoapplication/config/plivo.phpAuth ID, auth token, sender
Bulkerapplication/config/bulker.phpapiBaseUrl, auth_key, sender_name

Apifon (Registry)

GroupKeyDescription
APIFONENABLEDEnable/disable Apifon
APIFONSENDER_IDSMS sender name
APIFONTTLViber TTL before SMS fallback (seconds)
APIFONENABLE_IMEnable Viber channel
APIFONAPI_TOKENOAuth2 client_id
APIFONAPI_KEYOAuth2 client_secret

Cron Jobs

JobScheduleDescription
AdvSendEmailAndSMSBasedOnFromStoreStatusEvent-driven / cronSends SMS when order reaches from_store status
AdvSendEmailAndSMSBasedOnSentStatusEvent-driven / cronSends SMS when order is shipped
AdvGetBulkerSmsStatusPeriodicPolls Bulker API for delivery status of buffered messages

Client Extension Points

Provider Selection

The SMS provider is selected per client via the $provider parameter passed to sendSms() / sendSmsMsg(). The calling controller or job determines the provider based on client configuration.

Phone Validation

The isValidPhoneForSms() and convertToValidPhoneForSms() functions are Greece-specific (3069/003069/69 prefix patterns). Clients outside Greece must override these functions in application/helpers/sms_helper.php. The source code contains TODO comments acknowledging this limitation.

Message Templates

SMS and Viber message content is managed through the language system:

  • sms.order.message.prefix / sms.order.message.suffix -- Standard SMS
  • sms.order.message.saturday.prefix / sms.order.message.saturday.suffix -- Saturday delivery SMS
  • viber.order.from_store.message / viber.order.from_store.message.saturday -- Viber messages

Override these in application/language/{lang}/ for custom message wording.

Custom Providers

Add a new provider by:

  1. Adding a new case in sendSmsMsg() switch statement (client repo: override sms_helper.php)
  2. Creating the provider class in application/libraries/ or custom/
  3. Adding config file in application/config/

Business Rules

  1. Friday/Saturday handling: If the SMS is triggered on a Friday (dayOfWeek == 5), a different message template is used to inform the customer about Saturday delivery.
  2. Phone normalization: Greek mobile numbers are accepted in three formats and normalized to the international 30xx format:
    • 3069xxxxxxxx (12 digits) -- already normalized
    • 003069xxxxxxxx (14 digits) -- strip leading 00
    • 69xxxxxxxx (10 digits) -- prepend 30
  3. Communication history: Every order SMS is logged in customer_message_history for audit and customer service reference.
  4. Viber priority: YubotoOmni sends Viber first (priority 0), with SMS as fallback (priority 1). Apifon uses a TTL-based approach: if Viber delivery is not confirmed within the TTL window, SMS is sent automatically.
  5. Payment-specific SMS: getValidPhoneForPayway() only returns a valid phone for paid_at_store and paybybank payment methods, ensuring SMS is only sent for relevant order types.
  6. Bulker status polling: Orders sent via Bulker have their delivery status polled by a cron job. The sms_status field tracks the delivery lifecycle (sent -> delivered/undelivered/buffered).
  7. Fire-and-forget: Most providers (except Bulker) operate on a fire-and-forget model -- the SMS is sent and no delivery confirmation is tracked beyond the initial success/failure response.
  8. Gift card SMS: Gift card activations can trigger SMS notifications via GiftCardSendSms, using the same unified dispatcher.