Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit f3d858e

Browse files
authored
Merge pull request #78 from Zenfulcode/58-mobilepay-webhooks
58 mobilepay webhooks
2 parents 329f431 + b550214 commit f3d858e

30 files changed

Lines changed: 697 additions & 94 deletions

src/main/java/com/zenfulcode/commercify/commercify/OrderStatus.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
public enum OrderStatus {
44
PENDING, // Order has been created but not yet confirmed
5-
CONFIRMED, // Order has been confirmed by the customer
5+
PAID, // Order has been confirmed by the customer
66
SHIPPED, // Order has been shipped
77
COMPLETED, // Order has been delivered
88
CANCELLED, // Order has been cancelled
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.zenfulcode.commercify.commercify.api.requests;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import lombok.Builder;
6+
7+
@Builder
8+
@JsonIgnoreProperties(ignoreUnknown = true)
9+
public record WebhookPayload(
10+
@JsonProperty("msn") String msn,
11+
@JsonProperty("reference") String reference,
12+
@JsonProperty("pspReference") String pspReference,
13+
@JsonProperty("name") String name,
14+
@JsonProperty("amount") Object amount,
15+
@JsonProperty("timestamp") String timestamp,
16+
@JsonProperty("idempotencyKey") String idempotencyKey,
17+
@JsonProperty("success") boolean success
18+
) {
19+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.zenfulcode.commercify.commercify.config;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.mail.javamail.JavaMailSender;
7+
import org.springframework.mail.javamail.JavaMailSenderImpl;
8+
9+
import java.util.Properties;
10+
11+
@Configuration
12+
public class MailConfig {
13+
@Value("${spring.mail.username}")
14+
private String username;
15+
@Value("${spring.mail.password}")
16+
private String password;
17+
@Value("${spring.mail.host}")
18+
private String host;
19+
@Value("${spring.mail.port}")
20+
private int port;
21+
22+
@Value("${spring.mail.properties.mail.smtp.starttls.enable}")
23+
private boolean tls = true;
24+
@Value("${spring.mail.properties.mail.smtp.auth}")
25+
private boolean auth = true;
26+
27+
@Bean
28+
public JavaMailSender getJavaMailSender() {
29+
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
30+
mailSender.setHost(host);
31+
mailSender.setPort(port);
32+
33+
mailSender.setUsername(username);
34+
mailSender.setPassword(password);
35+
36+
Properties props = mailSender.getJavaMailProperties();
37+
props.put("mail.transport.protocol", "smtp");
38+
props.put("mail.smtp.auth", auth);
39+
props.put("mail.smtp.starttls.enable", tls);
40+
props.put("mail.debug", "true");
41+
42+
return mailSender;
43+
}
44+
}

src/main/java/com/zenfulcode/commercify/commercify/config/SecurityConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
@RequiredArgsConstructor
2525
@EnableRetry
2626
public class SecurityConfig {
27-
2827
private final JwtAuthenticationFilter jwtAuthFilter;
2928
private final AuthenticationProvider authenticationProvider;
3029

@@ -35,7 +34,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
3534
.requestMatchers(
3635
"/api/v1/auth/**",
3736
"/api/v1/products/active",
38-
"/api/v1/products/{id}").permitAll()
37+
"/api/v1/products/{id}",
38+
"/api/v1/payments/mobilepay/callback").permitAll()
3939
.anyRequest().authenticated()
4040
)
4141
.sessionManagement(smc -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

src/main/java/com/zenfulcode/commercify/commercify/dto/CustomerDetailsDTO.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ public class CustomerDetailsDTO {
1212
private String lastName;
1313
private String email;
1414
private String phone;
15+
16+
public String getFullName() {
17+
return firstName + " " + lastName;
18+
}
1519
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.zenfulcode.commercify.commercify.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
import org.hibernate.annotations.CreationTimestamp;
6+
import org.hibernate.annotations.UpdateTimestamp;
7+
8+
import java.time.Instant;
9+
10+
@Entity
11+
@Table(name = "webhook_configs")
12+
@Getter
13+
@Setter
14+
@Builder
15+
@NoArgsConstructor
16+
@AllArgsConstructor
17+
public class WebhookConfigEntity {
18+
@Id
19+
@GeneratedValue(strategy = GenerationType.IDENTITY)
20+
private Long id;
21+
22+
@Column(name = "provider", nullable = false)
23+
private String provider;
24+
25+
@Column(name = "webhook_url", nullable = false)
26+
private String webhookUrl;
27+
28+
@Column(name = "webhook_secret", nullable = false)
29+
private String webhookSecret;
30+
31+
@Column(name = "created_at", nullable = false)
32+
@CreationTimestamp
33+
private Instant createdAt;
34+
35+
@Column(name = "updated_at")
36+
@UpdateTimestamp
37+
private Instant updatedAt;
38+
}

src/main/java/com/zenfulcode/commercify/commercify/flow/OrderStateFlow.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ public OrderStateFlow() {
1515

1616
// Initial state -> Confirmed or Cancelled
1717
validTransitions.put(OrderStatus.PENDING, Set.of(
18-
OrderStatus.CONFIRMED,
18+
OrderStatus.PAID,
1919
OrderStatus.CANCELLED
2020
));
2121

2222
// Payment received -> Processing or Cancelled
23-
validTransitions.put(OrderStatus.CONFIRMED, Set.of(
23+
validTransitions.put(OrderStatus.PAID, Set.of(
2424
OrderStatus.SHIPPED,
2525
OrderStatus.CANCELLED
2626
));
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.zenfulcode.commercify.commercify.integration;
2+
3+
public record WebhookRegistrationResponse(
4+
String secret,
5+
String id
6+
) {
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.zenfulcode.commercify.commercify.integration;
2+
3+
public record WebhookSubscribeRequest(String callbackUrl) {
4+
}

src/main/java/com/zenfulcode/commercify/commercify/integration/mobilepay/MobilePayController.java

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package com.zenfulcode.commercify.commercify.integration.mobilepay;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.zenfulcode.commercify.commercify.api.requests.PaymentRequest;
6+
import com.zenfulcode.commercify.commercify.api.requests.WebhookPayload;
47
import com.zenfulcode.commercify.commercify.api.responses.PaymentResponse;
8+
import com.zenfulcode.commercify.commercify.integration.WebhookSubscribeRequest;
9+
import jakarta.servlet.http.HttpServletRequest;
510
import lombok.RequiredArgsConstructor;
611
import lombok.extern.slf4j.Slf4j;
712
import org.springframework.http.ResponseEntity;
@@ -27,28 +32,64 @@ public ResponseEntity<PaymentResponse> createPayment(@RequestBody PaymentRequest
2732

2833
@PostMapping("/callback")
2934
public ResponseEntity<String> handleCallback(
30-
@RequestParam String paymentReference,
31-
@RequestParam String status) {
35+
@RequestBody String body,
36+
HttpServletRequest request) {
37+
String date = request.getHeader("x-ms-date");
38+
String contentSha256 = request.getHeader("x-ms-content-sha256");
39+
String authorization = request.getHeader("Authorization");
40+
3241
try {
33-
mobilePayService.handlePaymentCallback(paymentReference, status);
42+
// First authenticate the request with the raw string payload
43+
mobilePayService.authenticateRequest(date, contentSha256, authorization, body, request);
44+
log.info("MP Webhook authenticated");
45+
46+
// Convert the string payload to WebhookPayload object
47+
ObjectMapper objectMapper = new ObjectMapper();
48+
WebhookPayload webhookPayload = objectMapper.readValue(body, WebhookPayload.class);
49+
50+
// Pass the converted payload to handlePaymentCallback
51+
mobilePayService.handlePaymentCallback(webhookPayload);
3452
return ResponseEntity.ok("Callback processed successfully");
53+
} catch (JsonProcessingException e) {
54+
log.error("Error parsing webhook payload", e);
55+
return ResponseEntity.badRequest().body("Invalid payload format");
3556
} catch (Exception e) {
3657
log.error("Error processing MobilePay callback", e);
3758
return ResponseEntity.badRequest().body("Error processing callback");
3859
}
3960
}
4061

4162
@PreAuthorize("hasRole('ADMIN')")
42-
@PostMapping("/webhook")
43-
public ResponseEntity<String> handleWebhook(
44-
@RequestParam String paymentReference,
45-
@RequestParam String status) {
63+
@PostMapping("/webhooks")
64+
public ResponseEntity<?> registerWebhooks(@RequestBody WebhookSubscribeRequest request) {
4665
try {
47-
mobilePayService.handlePaymentCallback(paymentReference, status);
48-
return ResponseEntity.ok("Callback processed successfully");
66+
mobilePayService.registerWebhooks(request.callbackUrl());
67+
return ResponseEntity.ok("Webhooks registered successfully");
4968
} catch (Exception e) {
50-
log.error("Error processing MobilePay callback", e);
51-
return ResponseEntity.badRequest().body("Error processing callback");
69+
return ResponseEntity.badRequest().body("Error registering webhooks");
70+
}
71+
}
72+
73+
@PreAuthorize("hasRole('ADMIN')")
74+
@DeleteMapping("/webhooks/{id}")
75+
public ResponseEntity<?> deleteWebhook(@PathVariable String id) {
76+
try {
77+
mobilePayService.deleteWebhook(id);
78+
return ResponseEntity.ok("Webhook deleted successfully");
79+
} catch (RuntimeException e) {
80+
return ResponseEntity.badRequest().body("Error deleting webhook");
81+
}
82+
}
83+
84+
@PreAuthorize("hasRole('ADMIN')")
85+
@GetMapping("/webhooks")
86+
public ResponseEntity<?> getWebhooks() {
87+
try {
88+
System.out.println("Getting webhooks");
89+
Object response = mobilePayService.getWebhooks();
90+
return ResponseEntity.ok(response);
91+
} catch (RuntimeException e) {
92+
return ResponseEntity.badRequest().body("Error getting webhook");
5293
}
5394
}
5495
}

0 commit comments

Comments
 (0)