Port Twilio SMS integration from CFML to PHP

Add sendSMS() to helpers.php using Twilio REST API with cURL,
credentials loaded from config/twilio.json. Wire into sendOTP,
loginOTP, and sendLoginOTP endpoints, replacing TODO stubs.
SMS is auto-skipped on dev environments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-03-14 16:02:34 -07:00
parent 4d806d4e1e
commit 08ef54976f
5 changed files with 66 additions and 14 deletions

View file

@ -37,13 +37,9 @@ $otp = random_int(100000, 999999);
queryTimed("UPDATE Users SET MobileVerifyCode = ? WHERE ID = ?", [$otp, $user['ID']]);
// Send OTP via Twilio (skip on dev)
$smsMessage = 'Code saved (SMS skipped in dev)';
$dev = isDev();
if (!$dev) {
// TODO: Twilio integration
$smsMessage = 'Login code sent';
}
$smsResult = sendSMS("+1{$phone}", "Your Payfrit code is: {$otp}");
$smsMessage = $smsResult['success'] ? 'Login code sent' : ('SMS failed - ' . $smsResult['message']);
$resp = [
'OK' => true,

View file

@ -66,10 +66,10 @@ try {
[$uid, $code]
);
// Send OTP via email or SMS (skip on dev)
// Send OTP via SMS or email (skip on dev)
if (!$dev) {
if (!empty($phone) && !empty($user['ContactNumber'])) {
// TODO: Twilio SMS
sendSMS("+1" . $user['ContactNumber'], "Your Payfrit login code is: {$code}. It expires in 10 minutes.");
} else {
// TODO: Email sending
}

View file

@ -46,13 +46,9 @@ if ($existing) {
}
// Send OTP via Twilio (skip on dev)
$smsMessage = 'Code saved (SMS skipped in dev)';
$dev = isDev();
if (!$dev) {
// TODO: Twilio integration
$smsMessage = 'Code sent';
}
$smsResult = sendSMS("+1{$phone}", "Your Payfrit code is: {$otp}");
$smsMessage = $smsResult['success'] ? 'Code sent' : ('SMS failed - ' . $smsResult['message']);
$resp = [
'OK' => true,

View file

@ -237,6 +237,61 @@ function generateUUID(): string {
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
}
// ============================================
// TWILIO SMS
// ============================================
/**
* Send an SMS via Twilio REST API.
* Returns ['success' => bool, 'message' => string].
* Skips sending on dev (returns success with note).
*/
function sendSMS(string $to, string $body): array {
if (isDev()) {
return ['success' => true, 'message' => 'SMS skipped in dev'];
}
$configPath = realpath(__DIR__ . '/../config/twilio.json');
if (!$configPath || !file_exists($configPath)) {
return ['success' => false, 'message' => 'Twilio config not found'];
}
$config = json_decode(file_get_contents($configPath), true);
$sid = $config['accountSid'] ?? '';
$token = $config['authToken'] ?? '';
$from = $config['fromNumber'] ?? '';
if (empty($sid) || empty($token) || empty($from)) {
return ['success' => false, 'message' => 'Twilio config incomplete'];
}
$url = "https://api.twilio.com/2010-04-01/Accounts/{$sid}/Messages.json";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERPWD => "{$sid}:{$token}",
CURLOPT_POSTFIELDS => http_build_query([
'From' => $from,
'To' => $to,
'Body' => $body,
]),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 201) {
return ['success' => true, 'message' => 'Message sent'];
}
$parsed = json_decode($response, true);
$errMsg = $parsed['message'] ?? "HTTP $httpCode";
return ['success' => false, 'message' => $errMsg];
}
// ============================================
// AUTH MIDDLEWARE
// ============================================

5
config/twilio.json Normal file
View file

@ -0,0 +1,5 @@
{
"accountSid": "AC3e218c8f3496f2e3f95d3f9bb943f65a",
"authToken": "493df16db970ca6cc141768de2db7db3",
"fromNumber": "+16506678425"
}