Skip to content

Transaction parsing sms gateway poc#1354

Open
beesaferoot wants to merge 20 commits intomainfrom
transaction-parsing-sms-gateway-poc
Open

Transaction parsing sms gateway poc#1354
beesaferoot wants to merge 20 commits intomainfrom
transaction-parsing-sms-gateway-poc

Conversation

@beesaferoot
Copy link
Copy Markdown
Contributor

@beesaferoot beesaferoot commented Mar 9, 2026

Makes progress on #1345

#1351 should be merged before this.

Brief summary of the change made

This PR expands on the bi-directional sms communication feature. With these changes we are creating use-case to support recording transactions from Mobile money providers through sms receipts.

MPM uses the sms gateway to obtain the receipts and does sms parsing using a flexible Parsing rule configured by the user as a plugin.

Are there any other side effects of this change that we should be aware of?

Describe how you tested your changes?

Pull Request checklist

Please confirm you have completed any of the necessary steps below.

  • Meaningful Pull Request title and description
  • Changes tested as described above
  • Added appropriate documentation for the change.
  • Created GitHub issues for any relevant followup/future enhancements if appropriate.

@beesaferoot beesaferoot requested a review from dmohns March 9, 2026 11:22
@beesaferoot beesaferoot force-pushed the transaction-parsing-sms-gateway-poc branch from 067800e to 03b098b Compare March 10, 2026 20:03
Copy link
Copy Markdown
Member

@dmohns dmohns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. Leaving a few comments here.

  1. Can you explain what is the exact purpose of MovitelTransactionParser and VodacomTransactionParser? For example, they simply get converted to Regex?
  2. Is there a way, for each rule, to see the last X (maybe 15) messages that matched the filter? And, if they succesfully parsed?

Comment thread src/backend/app/Plugins/SmsTransactionParser/SmsParsing/SmsParserFactory.php Outdated
@beesaferoot
Copy link
Copy Markdown
Contributor Author

  1. Can you explain what is the exact purpose of MovitelTransactionParser and VodacomTransactionParser? For example, they simply get converted to Regex?

Originally the separation of MovitelTransactionParser and VodacomTransactionParser was to make parsing deserialization very flexible to updates, especially when updating the parsing rules.

However, with all the parsing rules having the same variables i.e txn ref, account, phone etc. I have now consolidated this into a single deserialization abstraction SmsTransactionParser. This make this simpler and eliminates bonding new parser rules manually to these classes.

Is there a way, for each rule, to see the last X (maybe 15) messages that matched the filter? And, if they succesfully parsed?

I think we can already get this information from the sms_transactions table, all that is needed is to hookup an page in the plugin that shows a paginated list for each parse rule (uniquely identified perhaphs by provider name)

@beesaferoot beesaferoot requested a review from dmohns April 2, 2026 08:09
Comment thread src/backend/app/Plugins/TextbeeSmsGateway/Console/Commands/FetchIncomingSms.php Outdated
Copy link
Copy Markdown
Member

@dmohns dmohns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small UI request: When I click on a place holder, the placeholder get's added at the end. Can you make this, so the placeholder gets added at cursor position?

Image Image

@dmohns
Copy link
Copy Markdown
Member

dmohns commented Apr 16, 2026

Is there a way, for each rule, to see the last X (maybe 15) messages that matched the filter? And, if they succesfully parsed?

I think we can already get this information from the sms_transactions table, all that is needed is to hookup an page in the plugin that shows a paginated list for each parse rule (uniquely identified perhaphs by provider name)

I meant something else, could we get the last X messages from sms table (!). Basically, this would help for debugging to see if a message has matched the filter. And to understand whether or not a sms_transaction has been created.

@dmohns
Copy link
Copy Markdown
Member

dmohns commented Apr 16, 2026

image

Can we add a tooltip here to guide the user? I.e. if this is a regex or substring match. Will empty match all?

@beesaferoot
Copy link
Copy Markdown
Contributor Author

image Can we add a tooltip here to guide the user? I.e. if this is a regex or substring match. Will empty match all?

Yes if the sender pattern is empty it matches every sms body.

 if (isset($rule->sender_pattern) && $rule->sender_pattern != '' && !preg_match($rule->sender_pattern, $sender)) {
         continue;
 }

 if (!preg_match($rule->pattern, $body, $matches)) {
          continue;
  }
            

@beesaferoot
Copy link
Copy Markdown
Contributor Author

Is there a way, for each rule, to see the last X (maybe 15) messages that matched the filter? And, if they succesfully parsed?

I think we can already get this information from the sms_transactions table, all that is needed is to hookup an page in the plugin that shows a paginated list for each parse rule (uniquely identified perhaphs by provider name)

I meant something else, could we get the last X messages from sms table (!). Basically, this would help for debugging to see if a message has matched the filter. And to understand whether or not a sms_transaction has been created.

Bear with me for a moment on this one. Couldn't we achieve the same thing using the SMS list page and the parsed message list page on the plugin? I may be missing something.

@beesaferoot beesaferoot requested a review from dmohns April 16, 2026 11:43
@beesaferoot
Copy link
Copy Markdown
Contributor Author

@dmohns I've addressed most of your comments, kindly review.

Comment on lines +1 to +14
<?php

declare(strict_types=1);

namespace App\Plugins\SmsTransactionParser\SmsParsing\Contracts;

use App\Plugins\SmsTransactionParser\SmsParsing\DTO\ParsedSmsData;

interface ISmsTransactionParser {
/**
* @param array<string, string> $regexMatches Named capture groups from the parsing rule regex
*/
public function parse(string $body, array $regexMatches): ?ParsedSmsData;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface is only used once. I think it's a left over. Can we remove it ?

```

This command fetches new messages from the TextBee API and processes them the same way the webhook would.
It runs automatically on a schedule when the plugin is active, so no manual setup is needed.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify what the schedule is? daily, weekly ?

Comment on lines +51 to +53
$address = $this->addressesService->getAddressByPhoneNumber(str_replace(' ', '', $phoneNumber));
$sender = $address instanceof Address ? $address->owner : null;
$senderId = $sender?->getKey();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails there are SMS with alphanumeric sender IDs. For example

array:3 [
  "sender" => "ALDI TALK"
  "body" => """
...
    """
  "receivedAt" => "2026-04-10T15:46:31.000Z"
]
[2026-04-16 15:31:16] development.ERROR: Command [textbee-sms-gateway:fetch-incoming-sms] failed {"exception":"libphonenumber\\NumberParseException","message":"The string supplied did not seem to be a phone number.","file":"/Users/daniel/Development/micropowermanager/src/backend/vendor/giggsey/libphonenumber-for-php-lite/src/PhoneNumberUtil.php","line":1480,"trace":"#0 /Users/daniel/Development/micropowermanager/src/backend/vendor/giggsey/libphonenumber-for-php-lite/src/PhoneNumberUtil.php(2945): libphonenumber\\PhoneNumberUtil->parseHelper('ALDITALK', NULL, false, true, Object(libphonenumber\\PhoneNumber))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're now handle edge cases like this.

@beesaferoot
Copy link
Copy Markdown
Contributor Author

Ready for review @dmohns

Comment on lines +51 to +63
$address = $this->addressesService->getAddressByPhoneNumber(str_replace(' ', '', $phoneNumber));
$sender = $address instanceof Address ? $address->owner : null;
$senderId = $sender?->getKey();

$sms = $this->smsService->createSms([
'receiver' => $address->phone ?? $phoneNumber,
'body' => $body,
'sender_id' => $senderId,
'direction' => Sms::DIRECTION_INCOMING,
'status' => Sms::STATUS_DELIVERED,
]);

event(new SmsStoredEvent($phoneNumber, $body, $sms));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something inconsistent here. This works different to the callbacks. I.e. it always creates records in sms table even for Spam SMS and for Transaction SMS.

Also, currently running this creates duplicate SMS records.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has now been addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants