GithubHelp home page GithubHelp logo

sparors / laravel-ussd Goto Github PK

View Code? Open in Web Editor NEW
58.0 9.0 32.0 144 KB

Create ussd with ease

Home Page: https://sparors.github.io/ussd-docs/

License: MIT License

PHP 100.00%
ussd laravel laravel-package php library package

laravel-ussd's Introduction

Laravel Ussd

Latest Version on Packagist Total Downloads Build Status

Build Ussd (Unstructured Supplementary Service Data) applications with laravel without breaking a sweat.

Installation

You can install the package via composer:

composer require sparors/laravel-ussd:^3.0-beta

For older version use

composer require sparors/laravel-ussd:^2.0

Laravel Ussd provides zero configuration out of the box. To publish the config, run the vendor publish command:

php artisan vendor:publish --provider="Sparors\Ussd\UssdServiceProvider" --tag=ussd-config

Usage

For older version look here: V2 README

Creating USSD menus

<?php

namespace App\Ussd\States;

use App\Ussd\Actions\TransferAccountAction;
use Sparors\Ussd\Attributes\Paginate;
use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Context;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Decisions\Equal;
use Sparors\Ussd\Decisions\Fallback;
use Sparors\Ussd\Decisions\In;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Record;
use Sparors\Ussd\Traits\WithPagination;

#[Transition(to: TransferAccountAction::class, match: new Equal(1))]
#[Transition(to: TransferAmountState::class, match: new In(2, 3), callback: [self::class, 'setTransferType'])]
#[Transition(to: NewAccountNameState::class, match: new Equal(4))]
#[Transition(to: HelplineState::class, match: new Equal(5))]
#[Transition(to: InvalidInputState::class, match: new Fallback())]
#[Paginate(next: new Equal('#'), previous: new Equal('0'))]
class CustomerMenuState implements State
{
    use WithPagination;

    public function render(): Menu
    {
        return Menu::build()
            ->line('Banc')
            ->listing($this->getItems(), page: $this->currentPage(), perPage: $this->perPage())
            ->when($this->hasPreviousPage(), fn (Menu $menu) => $menu->line('0. Previous'))
            ->when($this->hasNextPage(), fn (Menu $menu) => $menu->line('#. Next'))
            ->text('Powered by Sparors');
    }

    public function setTransferType(Context $context, Record $record)
    {
        $transferType = '2' === $context->input() ? 'deposit' : 'withdraw';

        $record->set('transfer_type', $transferType);
    }

    public function getItems(): array
    {
        return [
            'Transfer',
            'Deposit',
            'Withdraw',
            'New Account',
            'Helpline',
        ];
    }

    public function perPage(): int
    {
        return 3;
    }
}

An example of a final state

<?php

namespace App\Ussd\States;

use Sparors\Ussd\Attributes\Terminate;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Menu;
use Sparors\Ussd\Record;

#[Terminate]
class GoodByeState implements State
{
    public function render(Record $record): Menu
    {
       return Menu::build()->text('Bye bye ' . $record->get('name'));
    }
}

Due to some limitation with PHP 8.0, you can not pass class instance to attributes. So to overcome this limitation, you can pass an array with the full class path as the first element and the rest should be argument required. eg.

<?php

namespace App\Ussd\States;

use Sparors\Ussd\Attributes\Transition;
use Sparors\Ussd\Contracts\State;
use Sparors\Ussd\Menu;

#[Transition(MakePaymentAction::class, [Equal::class, 1])]
#[Transition(InvalidInputState::class, Fallback::class)]
class WelcomeState implements State
{
    public function render(): Menu
    {
       return Menu::build()->text('Welcome');
    }
}

Building USSD

<?php

namespace App\Http\Controllers;

use App\Ussd\Actions\MenuAction;
use App\Ussd\Responses\AfricasTalkingResponse;
use App\Ussd\States\WouldYouLikeToContinueState;
use Illuminate\Http\Request;
use Sparors\Ussd\Context;
use Sparors\Ussd\ContinuingMode;
use Sparors\Ussd\Ussd;

class UssdController extends Controller
{
    public function __invoke(Request $request)
    {
        $lastText = $request->input('text') ?? '';

        if (strlen($lastText) > 0) {
            $lastText = explode('*', $lastText);
            $lastText = end($lastText);
        }

        return Ussd::build(
            Context::create(
                $request->input('sessionId'),
                $request->input('phoneNumber'),
                $lastText
            )
            ->with(['phone_number' => $request->input('phoneNumber')])
        )
        ->useInitialState(MenuAction::class)
        ->useContinuingState(ContinuingMode::CONFIRM, now()->addMinute(), WouldYouLikeToContinueState::class)
        ->useResponse(AfricasTalkingResponse::class)
        ->run();
    }
}

Conditional Branching

Use USSD action to conditional decide which state should be the next.

<?php

namespace App\Http\Ussd\Actions;

use Sparors\Ussd\Contracts\Action;
use App\Http\Ussd\States\PaymentSuccessState;
use App\Http\Ussd\States\PaymentErrorState;

class MakePayment extends Action
{
    public function execute(Record $record): string
    {
        $response = Http::post('/payment', [
            'phone_number' => $record->phoneNumber
        ]);

        if ($response->ok()) {
            return PaymentSuccessState::class;
        }

        return PaymentErrorState::class;
    }
}

Group logic with USSD configurator

You can use configurator to simplify repetitive parts of your application so they can be shared easily.

<?php

namespace App\Http\Ussd\Configurators;

use Sparors\Ussd\Contracts\Configurator;

class Nsano implements Configurator
{
    public function configure(Ussd $ussd): void
    {
        $ussd->setResponse(function (string $message, int $terminating) {
            return [
                'USSDResp' => [
                    'action' => $termination ? 'prompt': 'input',
                    'menus' => '',
                    'title' => $message
                ]
            ];
        });
    }
}
?>

Testing

You can easily test how your ussd application with our testing utilities

<?php

namespace App\Tests\Feature;

use Sparors\Ussd\Ussd;

final class UssdTest extends TestCase
{
    public function test_ussd_runs()
    {
        Ussd::test(WelcomeState::class)
            ->additional(['network' => 'MTN', 'phone_number' => '123123123'])
            ->actingAs('isaac')
            ->start()
            ->assertSee('Welcome...')
            ->assertContextHas('network', 'MTN')
            ->assertContextHas('phone_number')
            ->assertContextMissing('name')
            ->input('1')
            ->assertSee('Now see the magic...')
            ->assertRecordHas('choice');
    }
}

Documentation

You'll find the documentation on https://github.com/sparors/laravel-ussd/wiki for V3 and https://sparors.github.io/ussd-docs for V2.

Change log

Please see the changelog for more information on what has changed recently.

Contributing

Please see contributing.md for details and a todolist.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

License

MIT. Please see the license file for more information.

laravel-ussd's People

Contributors

cybersai avatar maukoese avatar papamarfo avatar samuelmwangiw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-ussd's Issues

Skipping Session Steps

How do I skip through ussd session steps?
Example:
My Ussd Code is *500#

But I would like to go directly to the second step of my session journey by dialling
5001*3#

Quotes and line break

Congratulations on the work done for this package.
However, line returns do not work on multiple returns. The same is true for quotes "".
An example return:
"Welcome to OPSIT\r\n1.MSISDN\r\n\r\n2.KYC"
What I would like to have:
Welcome to OPSIT
1.MSISDN
2.KYC

How To Use Record class to Pass Value

I have gone through the documentation; I don't really understand how to use the $this->record to pass value to other States. I have to instantiate the new object from the Record::class; I tried to create one, but the constructor requires a Cache parameter. I don't know how to create that.
Please can I get example that shows how I can work with the Record class to pass values to other States?
Thank you

Feature Request: Resume session

A USSD session may be timed out or may end abruptly without a user completing a flow.

In my use case, we have a user fill out a form, we tried to limit the number of inputs so that the user can complete the session in one go.

But not every user is fast enough to respond to these prompts especially those on feature phones(torchlight + radio only). These people always suffer timed-out sessions because they switch the input method and the typical press 1-3 or more times to input a single letter.

I manually implemented a buggy solution which involved using:

  1. a middleware to intercept the request to decide whether the previous session is still valid(not expired)
  2. using a separate cache to store the compound key of the phone_number and session
  3. an event listener and handler which fires when the cache is written to. It checks the key of the write operation. Keys with certain prefixes like __preserved__ are stored permanently
  4. The middleware checks and attempts to reconstruct the previous session(if it exists), populate the record with the previous data, determines the last complete state, modifies the __init__ and __active__ keys and finally displays a state which prompts the user to resume from the last step or start a fresh session

This implementation was so buggy and hacky(messing with internal records etc).

A cleaner, baked-in, easy-to-opt-in solution will be a great feature to have.

A much simpler approach is to let provide something like a State Machine config.This will allow the USSD machine to know where to resume automatically. See Spatie's Laravel Model state for inspiration

Please do consider this issue ... I'm ready to share further details as needed though I may not be able to send a PR at the moment because I spend less time behind my computer these days.

How to save states screen into database with Eloquent ???

Hi.

Is there any way to save states (USSD screens) into database ??? Because Laravel USSD Sparors's States is not Contrôler.

If so, How to do it ???
In other words, How to save USSD MENUS into Database After creating migrations ???

Please inform us.

Class 'Redis' Not Found

I already have redis and phpredis extension installed and configured for other purposes in my app and I can confirm it works however I keep getting that error whenever I use 'redis' as the USSD_STORE in my env.

Retrieving All sessions

I want to be able to create a reporting dashboard for ussd sessions that are running. Is there a way to do that?

Resume timed-out USSD sessions

I have an issue with my sessions to time out mostly, can you share trick on how to resume-timed-out USSD sessions? I have a session where user input full names and address, tin numbers ,etc.
Secondary it seems the script took longtime to respond sometimes,I do not know whether is my hosting issue or script itself or internet connection!
Regards

How do i access the Record Object of a particular Machine Session

Hi guys 👋 ,
I have recently fallen in love ❤️ with this project. Thank you for your work.

I would love to know how to retrieve data saved in record for a particular machine session.
After the user interacts with my application i would like to be able to access the data saved in record from another class which is neither a State or Action class.

Below is how my code looks like in my route

Route::any('/test', function (Request $request) {
    $ussd = Ussd::machine()
        ->setSessionId($request->Mobile)
        ->setInitialState(\App\Http\Ussd\States\Home::class)
        ->setNetworkFromRequest('Operator')
        ->setPhoneNumberFromRequest('Mobile')
        ->setInput(request()->input('Type') == env('USSD_INITIAL_TYPE') ? env('BEINSURED_DEFAULT_MSG') : $request->Message)
        ->setResponse(function (string $message) {
            return [
                'Message' => $message,
                'Type' => session()->get('ussd.type') !== 'Release' ? 'Response' : 'Release',
                "MaskNextRoute" => true,
            ];
        })
        ->setStore('redis');

    return response()->json($ussd->run());
});

How do i achieve this 🙏

Loading Dynamic Data

I have my business logic within Services. My menus are all dynamic menus, the data displayed comes from my DB. Example on my first WelcomeState I display a list of clients, this data is loaded from my database, I already have a function in my service that does this, how do I inject this service to be used within the state?

Or is there a way to pass dynamic data to each State/Action?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.