Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@ComponentScan(basePackages = "com.restroly.qrmenu")
@EnableJpaRepositories(basePackages = "com.restroly.qrmenu")
@EntityScan(basePackages = "com.restroly.qrmenu")
@EnableAsync
public class RestaurantApplication extends SpringBootServletInitializer {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.restroly.qrmenu.order.dto.UpdateOrderStatusRequest;
import com.restroly.qrmenu.order.service.OrderService;
import com.restroly.qrmenu.payment.service.PaymentService;
import com.restroly.qrmenu.whatsapp.service.WhatsappOrderNotificationService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -35,6 +36,8 @@ public class OrderController {
private final PaymentService paymentService = null;
@Autowired
private final OrderService orderService = null;
@Autowired
private final WhatsappOrderNotificationService whatsapp = null;


//private final OrderService orderService;
Expand All @@ -46,6 +49,9 @@ public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody CreateOrder
String paymentUrl = paymentService.generatePaymentLink(response.getTotalAmount(), response.getOrderId(), response.getPaymentLink());
//Genarated payment link is stored in response object to send it to client and use it for payment
response.setPaymentLink(paymentUrl);
//Sending whatsapp notification
if(response.getCustomerName() == null) response.setCustomerName("Customer");
if(response.getCustomerPhone() != null) whatsapp.sendOrderConfirmation(response);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

Expand All @@ -67,12 +73,19 @@ public ResponseEntity<List<OrderResponse>> getActiveOrders(@PathVariable Long br
@PatchMapping("/{orderId}/status")
public ResponseEntity<OrderResponse> updateOrderStatus(@PathVariable Long orderId,
@Valid @RequestBody UpdateOrderStatusRequest request) {
return ResponseEntity.ok(orderService.updateOrderStatus(orderId, request.getStatus()));
OrderResponse response = orderService.updateOrderStatus(orderId, request.getStatus());
//Sending whatsapp notification
if(response.getCustomerName() == null) response.setCustomerName("Customer");
if(response.getCustomerPhone() != null) whatsapp.sendOrderStatusUpdate(response);
return ResponseEntity.ok(response);
}

@PostMapping("/{orderId}/cancel")
public ResponseEntity<Void> cancelOrder(@PathVariable Long orderId) {
orderService.cancelOrder(orderId);
OrderResponse response=orderService.cancelOrder(orderId);
//sending cancel message
if(response.getCustomerName() == null) response.setCustomerName("Customer");
if(response.getCustomerPhone() != null) whatsapp.sendOrderStatusUpdate(response);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ public interface OrderService {

OrderResponse updateOrderStatus(Long orderId, OrderStatus status);

void cancelOrder(Long orderId);
OrderResponse cancelOrder(Long orderId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,13 @@ public OrderResponse updateOrderStatus(Long orderId, OrderStatus status) {
}

@Override
public void cancelOrder(Long orderId) {
public OrderResponse cancelOrder(Long orderId) {
log.debug("Cancelling order with id: {}", orderId);
Order order = findOrderById(orderId);
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
Order canceledOrder = orderRepository.save(order);
log.info("Order {} cancelled", orderId);
return orderMapper.toResponse(canceledOrder);
}

private Order findOrderById(Long orderId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.restroly.qrmenu.whatsapp.service;

import com.restroly.qrmenu.order.dto.OrderResponse;

public interface WhatsappOrderNotificationService{
void sendOrderConfirmation(OrderResponse order);
void sendOrderStatusUpdate(OrderResponse order);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.restroly.qrmenu.whatsapp.service;

import java.util.List;
import java.util.Map;

public interface WhatsappService {
void sendTemplateMessage(String toPhoneNumber, String templateName, List<Map<String, String>> bodyParameters);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.restroly.qrmenu.whatsapp.service.impl;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.restroly.qrmenu.order.dto.OrderResponse;
import com.restroly.qrmenu.whatsapp.service.WhatsappOrderNotificationService;

import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class WhatsappOrderNotificationServiceImpl extends WhatsappServiceImpl implements WhatsappOrderNotificationService{
@Autowired
public WhatsappOrderNotificationServiceImpl(RestTemplateBuilder builder) {
super(builder);
}
@Value("${whatsapp.message.template.order-confirmation}")
public String orderConfirmation;
@Value("${whatsapp.message.template.status-update}")
public String orderUpdateStatus;

/**
* Sends the initial order confirmation using an approved Meta Template.
*/
@Async
Comment thread
SOUVIK-D10 marked this conversation as resolved.
public void sendOrderConfirmation(OrderResponse order) {
String templateName = orderConfirmation; // The exact name of your approved template in Meta

// The parameters MUST be in the exact order as {{1}}, {{2}}, {{3}}, etc., in your Meta template
List<Map<String, String>> bodyParameters = List.of(
Map.of("type", "text", "text", order.getCustomerName()), // {{1}}
Comment thread
SOUVIK-D10 marked this conversation as resolved.
Map.of("type", "text", "text", String.valueOf(order.getOrderId())), // {{2}}
Map.of("type", "text", "text", order.getBranchName()), // {{3}}
Map.of("type", "text", "text", String.valueOf(order.getTableNumber())), // {{4}}
Map.of("type", "text", "text", order.getTotalAmount().toString()), // {{5}}
Map.of("type", "text", "text", order.getPaymentLink() != null ? order.getPaymentLink() : "Payment Due") // {{6}}
);
sendTemplateMessage(order.getCustomerPhone(), templateName, bodyParameters);
}
/**
* Sends an update when the order status changes.
*/
@Async
public void sendOrderStatusUpdate(OrderResponse order) {
String templateName = orderUpdateStatus;

// The parameters MUST match the exact order of {{1}}, {{2}}, {{3}} in your Meta template
List<Map<String, String>> bodyParameters = List.of(
Map.of("type", "text", "text", String.valueOf(order.getOrderId())), // {{1}}
Map.of("type", "text", "text", order.getCustomerName()), // {{2}}
Map.of("type", "text", "text", order.getStatus().name()) // {{3}}
Comment thread
SOUVIK-D10 marked this conversation as resolved.
);

sendTemplateMessage(order.getCustomerPhone(), templateName, bodyParameters);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.restroly.qrmenu.whatsapp.service.impl;

import com.restroly.qrmenu.whatsapp.service.WhatsappService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
@Slf4j
public abstract class WhatsappServiceImpl implements WhatsappService {
private final RestTemplate restTemplate;

@Value("${whatsapp.api.url:https://graph.facebook.com/v25.0/}")
private String apiUrl;

@Value("${whatsapp.api.phone-number-id}")
private String phoneNumberId;

@Value("${whatsapp.api.token}")
private String accessToken;

@Autowired
public WhatsappServiceImpl(RestTemplateBuilder builder) {
this.restTemplate = builder
.setConnectTimeout(Duration.ofMillis(5000)) // Time to establish connection
.setReadTimeout(Duration.ofMillis(5000)) // Time to wait for data
.build();
}

/**
* Core method to execute the HTTP POST request to Meta's Cloud API.
*/
public void sendTemplateMessage(String toPhoneNumber, String templateName,
List<Map<String, String>> bodyParameters) {
try {
String url = apiUrl + phoneNumberId + "/messages";

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(accessToken);

// Constructing the JSON payload for a Template Message
Map<String, Object> payload = Map.of(
"messaging_product", "whatsapp",
"recipient_type", "individual",
"to", formatPhoneNumber(toPhoneNumber),
Comment thread
SOUVIK-D10 marked this conversation as resolved.
"type", "template",
"template", Map.of(
"name", templateName,
"language", Map.of("code", "en_US"),
"components", List.of(
Map.of(
"type", "body",
"parameters", bodyParameters))));

HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(payload, headers);

for (int i = 1; i <= 3; i++) {
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
log.info("WhatsApp template '{}' sent successfully to {}", templateName, toPhoneNumber);
break;
} else {
log.warn("Failed to send WhatsApp template. Status: {}, Response: {} for try {}",
response.getStatusCode(), response.getBody(),i);
}
}

} catch (Exception e) {
log.error("Exception occurred while sending WhatsApp template to {}: {}", toPhoneNumber, e.getMessage());
}
}

/**
* Utility to ensure the phone number doesn't have '+' or spaces,
* as required by the WhatsApp API.
*/
private String formatPhoneNumber(String phone) {
if (phone == null)
return "";
return phone.replaceAll("[^0-9]", "");
}
}
9 changes: 9 additions & 0 deletions RestroHub/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ google.oauth.client-id=${GOOGLE_OAUTH_CLIENT_ID:YOUR_GOOGLE_OAUTH_CLIENT_ID_HERE
#--- UPI Link generation used as instructed ---
payment.payee.name=Restroly

# ===============================
# WhatsApp
# ===============================
whatsapp.api.url=${WHATSAPP_URL:https://graph.facebook.com/v25.0/}
whatsapp.api.phone-number-id= ${PHONE_ID:your-phone-number-id}
whatsapp.api.token= ${WHATSAPP_ACCESS_TOKEN:your-permanent-access-token}
whatsapp.message.template.order-confirmation=${WHATSAPP_ORDER_CONFIRMATION_TEMPLATE_NAME:approved_template}
whatsapp.message.template.status-update=${WHATSAPP_ORDER_CONFIRMATION_TEMPLATE_NAME:approved_template}

# ===============================
# Security / CORS
# ===============================
Expand Down