Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.
Merged
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 @@ -2,7 +2,7 @@

public enum OrderStatus {
PENDING, // Order has been created but not yet confirmed
CONFIRMED, // Order has been confirmed by the customer
PAID, // Order has been confirmed by the customer
SHIPPED, // Order has been shipped
COMPLETED, // Order has been delivered
CANCELLED, // Order has been cancelled
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.zenfulcode.commercify.commercify.api.requests;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;

@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public record WebhookPayload(
@JsonProperty("msn") String msn,
@JsonProperty("reference") String reference,
@JsonProperty("pspReference") String pspReference,
@JsonProperty("name") String name,
@JsonProperty("amount") Object amount,
@JsonProperty("timestamp") String timestamp,
@JsonProperty("idempotencyKey") String idempotencyKey,
@JsonProperty("success") boolean success
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.zenfulcode.commercify.commercify.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Properties;

@Configuration
public class MailConfig {
@Value("${spring.mail.username}")
private String username;
@Value("${spring.mail.password}")
private String password;
@Value("${spring.mail.host}")
private String host;
@Value("${spring.mail.port}")
private int port;

@Value("${spring.mail.properties.mail.smtp.starttls.enable}")
private boolean tls = true;
@Value("${spring.mail.properties.mail.smtp.auth}")
private boolean auth = true;

@Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
mailSender.setPort(port);

mailSender.setUsername(username);
mailSender.setPassword(password);

Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", auth);
props.put("mail.smtp.starttls.enable", tls);
props.put("mail.debug", "true");

return mailSender;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
@RequiredArgsConstructor
@EnableRetry
public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;

Expand All @@ -35,7 +34,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers(
"/api/v1/auth/**",
"/api/v1/products/active",
"/api/v1/products/{id}").permitAll()
"/api/v1/products/{id}",
"/api/v1/payments/mobilepay/callback").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(smc -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ public class CustomerDetailsDTO {
private String lastName;
private String email;
private String phone;

public String getFullName() {
return firstName + " " + lastName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.zenfulcode.commercify.commercify.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.Instant;

@Entity
@Table(name = "webhook_configs")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WebhookConfigEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "provider", nullable = false)
private String provider;

@Column(name = "webhook_url", nullable = false)
private String webhookUrl;

@Column(name = "webhook_secret", nullable = false)
private String webhookSecret;

@Column(name = "created_at", nullable = false)
@CreationTimestamp
private Instant createdAt;

@Column(name = "updated_at")
@UpdateTimestamp
private Instant updatedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public OrderStateFlow() {

// Initial state -> Confirmed or Cancelled
validTransitions.put(OrderStatus.PENDING, Set.of(
OrderStatus.CONFIRMED,
OrderStatus.PAID,
OrderStatus.CANCELLED
));

// Payment received -> Processing or Cancelled
validTransitions.put(OrderStatus.CONFIRMED, Set.of(
validTransitions.put(OrderStatus.PAID, Set.of(
OrderStatus.SHIPPED,
OrderStatus.CANCELLED
));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.zenfulcode.commercify.commercify.integration;

public record WebhookRegistrationResponse(
String secret,
String id
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.zenfulcode.commercify.commercify.integration;

public record WebhookSubscribeRequest(String callbackUrl) {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.zenfulcode.commercify.commercify.integration.mobilepay;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zenfulcode.commercify.commercify.api.requests.PaymentRequest;
import com.zenfulcode.commercify.commercify.api.requests.WebhookPayload;
import com.zenfulcode.commercify.commercify.api.responses.PaymentResponse;
import com.zenfulcode.commercify.commercify.integration.WebhookSubscribeRequest;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
Expand All @@ -27,28 +32,64 @@ public ResponseEntity<PaymentResponse> createPayment(@RequestBody PaymentRequest

@PostMapping("/callback")
public ResponseEntity<String> handleCallback(
@RequestParam String paymentReference,
@RequestParam String status) {
@RequestBody String body,
HttpServletRequest request) {
String date = request.getHeader("x-ms-date");
String contentSha256 = request.getHeader("x-ms-content-sha256");
String authorization = request.getHeader("Authorization");

try {
mobilePayService.handlePaymentCallback(paymentReference, status);
// First authenticate the request with the raw string payload
mobilePayService.authenticateRequest(date, contentSha256, authorization, body, request);
log.info("MP Webhook authenticated");

// Convert the string payload to WebhookPayload object
ObjectMapper objectMapper = new ObjectMapper();
WebhookPayload webhookPayload = objectMapper.readValue(body, WebhookPayload.class);

// Pass the converted payload to handlePaymentCallback
mobilePayService.handlePaymentCallback(webhookPayload);
return ResponseEntity.ok("Callback processed successfully");
} catch (JsonProcessingException e) {
log.error("Error parsing webhook payload", e);
return ResponseEntity.badRequest().body("Invalid payload format");
} catch (Exception e) {
log.error("Error processing MobilePay callback", e);
return ResponseEntity.badRequest().body("Error processing callback");
}
}

@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/webhook")
public ResponseEntity<String> handleWebhook(
@RequestParam String paymentReference,
@RequestParam String status) {
@PostMapping("/webhooks")
public ResponseEntity<?> registerWebhooks(@RequestBody WebhookSubscribeRequest request) {
try {
mobilePayService.handlePaymentCallback(paymentReference, status);
return ResponseEntity.ok("Callback processed successfully");
mobilePayService.registerWebhooks(request.callbackUrl());
return ResponseEntity.ok("Webhooks registered successfully");
} catch (Exception e) {
log.error("Error processing MobilePay callback", e);
return ResponseEntity.badRequest().body("Error processing callback");
return ResponseEntity.badRequest().body("Error registering webhooks");
}
}

@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/webhooks/{id}")
public ResponseEntity<?> deleteWebhook(@PathVariable String id) {
try {
mobilePayService.deleteWebhook(id);
return ResponseEntity.ok("Webhook deleted successfully");
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body("Error deleting webhook");
}
}

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/webhooks")
public ResponseEntity<?> getWebhooks() {
try {
System.out.println("Getting webhooks");
Object response = mobilePayService.getWebhooks();
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body("Error getting webhook");
}
}
}
Loading
Loading