Require SMS number for manual sales
Browse files- API_DOCUMENTATION.md +1 -0
- src/routes/manualSales.routes.js +17 -15
API_DOCUMENTATION.md
CHANGED
|
@@ -1103,6 +1103,7 @@ Plan selection is mandatory. The backend copies the selected plan's real price,
|
|
| 1103 |
**Validation:**
|
| 1104 |
- `device_id` required
|
| 1105 |
- `plan_id` required
|
|
|
|
| 1106 |
- selected plan must belong to the selected device
|
| 1107 |
- selected plan must belong to the authenticated tenant
|
| 1108 |
- selected plan must be active
|
|
|
|
| 1103 |
**Validation:**
|
| 1104 |
- `device_id` required
|
| 1105 |
- `plan_id` required
|
| 1106 |
+
- `customer_phone` required so the generated token can be sent by SMS
|
| 1107 |
- selected plan must belong to the selected device
|
| 1108 |
- selected plan must belong to the authenticated tenant
|
| 1109 |
- selected plan must be active
|
src/routes/manualSales.routes.js
CHANGED
|
@@ -71,12 +71,16 @@ router.get('/', async (req, res) => {
|
|
| 71 |
// POST /api/manual-sales
|
| 72 |
router.post('/', async (req, res) => {
|
| 73 |
const { device_id, plan_id, customer_phone = null, notes = null } = req.body;
|
|
|
|
| 74 |
|
| 75 |
if (!device_id || !plan_id) {
|
| 76 |
return res.status(400).json({ error: 'device_id and plan_id are required' });
|
| 77 |
}
|
| 78 |
-
if (
|
| 79 |
-
return res.status(400).json({ error: 'customer_phone
|
|
|
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
if (notes !== null && typeof notes !== 'string') {
|
| 82 |
return res.status(400).json({ error: 'notes must be a string or null' });
|
|
@@ -172,7 +176,7 @@ router.post('/', async (req, res) => {
|
|
| 172 |
req.client.id,
|
| 173 |
device.id,
|
| 174 |
plan.id,
|
| 175 |
-
|
| 176 |
'other',
|
| 177 |
req.client.id,
|
| 178 |
notes?.trim() || null,
|
|
@@ -195,16 +199,12 @@ router.post('/', async (req, res) => {
|
|
| 195 |
|
| 196 |
await connection.commit();
|
| 197 |
|
| 198 |
-
const
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
cleanCustomerPhone,
|
| 205 |
-
messages.manualWifiCode(code, plan.name, duration, device.name, expiryPreview)
|
| 206 |
-
).catch(() => false));
|
| 207 |
-
}
|
| 208 |
|
| 209 |
res.status(201).json({
|
| 210 |
payment_id: paymentResult.insertId,
|
|
@@ -223,8 +223,10 @@ router.post('/', async (req, res) => {
|
|
| 223 |
duration_seconds: plan.duration_seconds,
|
| 224 |
notes: notes?.trim() || null,
|
| 225 |
sms_sent: smsSent,
|
| 226 |
-
expires_at_preview:
|
| 227 |
-
message:
|
|
|
|
|
|
|
| 228 |
});
|
| 229 |
} catch (err) {
|
| 230 |
await connection.rollback();
|
|
|
|
| 71 |
// POST /api/manual-sales
|
| 72 |
router.post('/', async (req, res) => {
|
| 73 |
const { device_id, plan_id, customer_phone = null, notes = null } = req.body;
|
| 74 |
+
const cleanCustomerPhone = typeof customer_phone === 'string' ? customer_phone.trim() : '';
|
| 75 |
|
| 76 |
if (!device_id || !plan_id) {
|
| 77 |
return res.status(400).json({ error: 'device_id and plan_id are required' });
|
| 78 |
}
|
| 79 |
+
if (typeof customer_phone !== 'string' || !cleanCustomerPhone) {
|
| 80 |
+
return res.status(400).json({ error: 'customer_phone is required so the token can be sent by SMS' });
|
| 81 |
+
}
|
| 82 |
+
if (cleanCustomerPhone.replace(/\D/g, '').length < 9) {
|
| 83 |
+
return res.status(400).json({ error: 'customer_phone must be a valid phone number' });
|
| 84 |
}
|
| 85 |
if (notes !== null && typeof notes !== 'string') {
|
| 86 |
return res.status(400).json({ error: 'notes must be a string or null' });
|
|
|
|
| 176 |
req.client.id,
|
| 177 |
device.id,
|
| 178 |
plan.id,
|
| 179 |
+
cleanCustomerPhone,
|
| 180 |
'other',
|
| 181 |
req.client.id,
|
| 182 |
notes?.trim() || null,
|
|
|
|
| 199 |
|
| 200 |
await connection.commit();
|
| 201 |
|
| 202 |
+
const duration = formatDuration(plan.duration_seconds);
|
| 203 |
+
const expiryPreview = formatTzExpiryPreview(plan.duration_seconds);
|
| 204 |
+
const smsSent = Boolean(await sms.sendSMS(
|
| 205 |
+
cleanCustomerPhone,
|
| 206 |
+
messages.manualWifiCode(code, plan.name, duration, device.name, expiryPreview)
|
| 207 |
+
).catch(() => false));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
res.status(201).json({
|
| 210 |
payment_id: paymentResult.insertId,
|
|
|
|
| 223 |
duration_seconds: plan.duration_seconds,
|
| 224 |
notes: notes?.trim() || null,
|
| 225 |
sms_sent: smsSent,
|
| 226 |
+
expires_at_preview: expiryPreview,
|
| 227 |
+
message: smsSent
|
| 228 |
+
? 'Token generated and SMS sent successfully'
|
| 229 |
+
: 'Token generated, but SMS failed to send',
|
| 230 |
});
|
| 231 |
} catch (err) {
|
| 232 |
await connection.rollback();
|