From 88244cb56f28193cd8cb0da78d5b8d290870284b Mon Sep 17 00:00:00 2001 From: TakahashiNguyen Date: Tue, 12 May 2026 05:22:08 +0000 Subject: [PATCH] fix: add payment features (#52) Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com> Reviewed-on: https://git.demonkernel.io.vn/FoodSurf/backend/pulls/52 --- .gitea/workflows/test.yaml | 10 ++- .releaserc.yaml | 2 +- .../com/drinkool/entities/ManagerEntity.java | 16 ++++ .../drinkool/services/CustomerService.java | 1 - .../com/drinkool/services/ManagerService.java | 41 ++++++++- .../com/drinkool/services/StaffService.java | 1 - .../controllers/ManagerController.java | 22 +++++ .../src/main/java/com/drinkool/dtos/Cart.java | 2 + .../main/java/com/drinkool/dtos/CartItem.java | 3 + .../com/drinkool/services/CartService.java | 87 +++++++++++++++---- .../src/main/resources/application.properties | 1 + .../drinkool/services/CartServiceTest.java | 10 ++- .../com/drinkool/services/EateryService.java | 22 ++--- .../src/main/resources/application.properties | 6 +- file-service/pom.xml | 4 +- .../com/drinkool/lib/dtos/ManagerSignup.java | 2 + 16 files changed, 190 insertions(+), 40 deletions(-) create mode 100644 cart-service/src/main/java/com/drinkool/controllers/ManagerController.java diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml index f1b7150..6a8b018 100644 --- a/.gitea/workflows/test.yaml +++ b/.gitea/workflows/test.yaml @@ -20,7 +20,10 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Install JDK and Maven + - name: Install dependencies with Proxy + env: + http_proxy: "http://host.docker.internal:3142" + https_proxy: "http://host.docker.internal:3142" run: | # Kiểm tra proxy có hoạt động không echo "Acquire::http::Proxy \"$http_proxy\";" > /etc/apt/apt.conf.d/80proxy @@ -48,7 +51,10 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Install JDK and Maven + - name: Install dependencies with Proxy + env: + http_proxy: "http://host.docker.internal:3142" + https_proxy: "http://host.docker.internal:3142" run: | # Kiểm tra proxy có hoạt động không echo "Acquire::http::Proxy \"$http_proxy\";" > /etc/apt/apt.conf.d/80proxy diff --git a/.releaserc.yaml b/.releaserc.yaml index 94648bc..7437b9a 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -13,7 +13,7 @@ plugins: mvn versions:set -DnewVersion=${nextRelease.version} && sed -i "/image:.*-service:/ s|:[^:]*$|:${nextRelease.version}|" k8s/base/*.yaml && if [ "${branch.name}" = "main" ]; then - mvn package -B -Pnative -Dquarkus.container-image.tag=${nextRelease.version} -DskipTests + mvn package -B -Pnative -Dquarkus.container-image.tag=${nextRelease.version} -DskipTests -Ppush fi - - "@saithodev/semantic-release-gitea" - giteaUrl: "https://git.demonkernel.io.vn/" diff --git a/account-service/src/main/java/com/drinkool/entities/ManagerEntity.java b/account-service/src/main/java/com/drinkool/entities/ManagerEntity.java index 8b08ce8..8649b9c 100644 --- a/account-service/src/main/java/com/drinkool/entities/ManagerEntity.java +++ b/account-service/src/main/java/com/drinkool/entities/ManagerEntity.java @@ -3,6 +3,7 @@ package com.drinkool.entities; import com.drinkool.Role; import com.drinkool.models.UserModel; import jakarta.persistence.*; +import jakarta.transaction.Transactional; import java.util.ArrayList; import java.util.List; @@ -17,7 +18,22 @@ public class ManagerEntity extends UserModel { ) public List staffs = new ArrayList<>(); + @Column(nullable = true) + public String bankAccount; + public ManagerEntity() { super(Role.Manager); } + + @Transactional + public void signup( + String name, + String phone, + String bankAccount, + String rawPassword + ) { + this.bankAccount = bankAccount; + + super.signup(name, phone, rawPassword); + } } diff --git a/account-service/src/main/java/com/drinkool/services/CustomerService.java b/account-service/src/main/java/com/drinkool/services/CustomerService.java index ee9f58d..a57abc9 100644 --- a/account-service/src/main/java/com/drinkool/services/CustomerService.java +++ b/account-service/src/main/java/com/drinkool/services/CustomerService.java @@ -65,7 +65,6 @@ public class CustomerService extends BaseService { return Response.accepted() .header(InternalValue.userId, customer.id.toString()) .header(InternalValue.role, Role.Customer) - .entity(customer) .build(); } diff --git a/account-service/src/main/java/com/drinkool/services/ManagerService.java b/account-service/src/main/java/com/drinkool/services/ManagerService.java index b9ef5f9..ddb77fe 100644 --- a/account-service/src/main/java/com/drinkool/services/ManagerService.java +++ b/account-service/src/main/java/com/drinkool/services/ManagerService.java @@ -8,6 +8,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; import jakarta.ws.rs.*; import jakarta.ws.rs.core.*; +import java.util.UUID; import org.eclipse.microprofile.reactive.messaging.*; @Path(Role.Manager) @@ -27,7 +28,12 @@ public class ManagerService extends BaseService { public Response signup(ManagerSignup input) { ManagerEntity newManager = new ManagerEntity(); - newManager.signup(input.name, input.phone, input.password); + newManager.signup( + input.name, + input.phone, + input.bankAccount, + input.password + ); ManagerCreate message = new ManagerCreate(); message.id = newManager.id; @@ -62,7 +68,38 @@ public class ManagerService extends BaseService { return Response.accepted() .header(InternalValue.userId, manager.id.toString()) .header(InternalValue.role, Role.Manager) - .entity(manager) .build(); } + + @GET + @Path("/{id}/payment-qr") + public Response getManagerPaymentQr( + @PathParam("id") UUID id, + @QueryParam("amount") Long amount, + @QueryParam("cartId") String cartId + ) { + ManagerEntity manager = ManagerEntity.findById(id); + if (manager == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("ManagerNotFound") + .build(); + } + + if (manager.bankAccount == null || manager.bankAccount.isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("ManagerHasNotConfiguredBankInfo") + .build(); + } + + String description = cartId; + + return Response.ok( + String.format( + "https://img.vietqr.io/image/%s-compact.png?amount=%d&addInfo=%s", + manager.bankAccount, + amount, + description + ) + ).build(); + } } diff --git a/account-service/src/main/java/com/drinkool/services/StaffService.java b/account-service/src/main/java/com/drinkool/services/StaffService.java index e948890..b8bcaef 100644 --- a/account-service/src/main/java/com/drinkool/services/StaffService.java +++ b/account-service/src/main/java/com/drinkool/services/StaffService.java @@ -67,7 +67,6 @@ public class StaffService extends BaseService { .header(InternalValue.userId, staff.id.toString()) .header(InternalValue.managerId, staff.serve.id.toString()) .header(InternalValue.role, Role.Staff) - .entity(staff) .build(); } } diff --git a/cart-service/src/main/java/com/drinkool/controllers/ManagerController.java b/cart-service/src/main/java/com/drinkool/controllers/ManagerController.java new file mode 100644 index 0000000..043950c --- /dev/null +++ b/cart-service/src/main/java/com/drinkool/controllers/ManagerController.java @@ -0,0 +1,22 @@ +package com.drinkool.controllers; + +import com.drinkool.Role; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Response; +import java.util.UUID; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "eatery") +@Path(Role.Manager) +public interface ManagerController { + @GET + @Path(Role.Manager + "/{id}/payment-qr") + public Response getManagerPaymentQr( + @PathParam("id") UUID id, + @QueryParam("amount") Long amount, + @QueryParam("cartId") String cartId + ); +} diff --git a/cart-service/src/main/java/com/drinkool/dtos/Cart.java b/cart-service/src/main/java/com/drinkool/dtos/Cart.java index 77ad94a..0466563 100644 --- a/cart-service/src/main/java/com/drinkool/dtos/Cart.java +++ b/cart-service/src/main/java/com/drinkool/dtos/Cart.java @@ -15,4 +15,6 @@ public class Cart { public UUID userId; public UUID eateryId; public List items; + public long totalAmount; + public String paymentQrUrl; } diff --git a/cart-service/src/main/java/com/drinkool/dtos/CartItem.java b/cart-service/src/main/java/com/drinkool/dtos/CartItem.java index 56c0fa0..bcb9e23 100644 --- a/cart-service/src/main/java/com/drinkool/dtos/CartItem.java +++ b/cart-service/src/main/java/com/drinkool/dtos/CartItem.java @@ -11,4 +11,7 @@ public class CartItem { public String productId; public Integer quantity; + + public Double priceAtTimeOfAdding; + public Double subTotal; } diff --git a/cart-service/src/main/java/com/drinkool/services/CartService.java b/cart-service/src/main/java/com/drinkool/services/CartService.java index be0e54c..7c2c6ed 100644 --- a/cart-service/src/main/java/com/drinkool/services/CartService.java +++ b/cart-service/src/main/java/com/drinkool/services/CartService.java @@ -2,19 +2,20 @@ package com.drinkool.services; import com.drinkool.InternalValue; import com.drinkool.controllers.EateryController; +import com.drinkool.controllers.ManagerController; import com.drinkool.dtos.Cart; import com.drinkool.dtos.CartItem; import io.quarkus.redis.datasource.RedisDataSource; import io.quarkus.redis.datasource.hash.HashCommands; import io.quarkus.redis.datasource.value.ValueCommands; import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.infrastructure.Infrastructure; import io.vertx.core.http.HttpServerRequest; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import java.time.Duration; import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.UUID; import org.eclipse.microprofile.graphql.GraphQLApi; @@ -35,6 +36,10 @@ public class CartService { @RestClient EateryController eateryController; + @Inject + @RestClient + ManagerController managerController; + private final HashCommands hashCommands; private final ValueCommands valueCommands; @@ -50,6 +55,7 @@ public class CartService { return eateryController .getEateryById(eateryId) + .emitOn(Infrastructure.getDefaultWorkerPool()) .onItem() .transform(response -> { if (response.getStatus() != Response.Status.OK.getStatusCode()) { @@ -58,6 +64,7 @@ public class CartService { String cartId = UUID.randomUUID().toString(); String redisKey = "cart:" + cartId; + String ownerId = response.getEntity().toString(); hashCommands.hset( redisKey, @@ -65,7 +72,9 @@ public class CartService { "_metadata_owner", userId != null ? userId : "GUEST", "_metadata_eatery", - eateryId.toString() + eateryId.toString(), + "_metadata_eateryManagerId", + ownerId ) ); @@ -87,27 +96,56 @@ public class CartService { @Query("getCart") public Cart getCart(@Name("cartId") UUID cartId) throws GraphQLException { String key = "cart:" + cartId; + String priceKey = "cart_prices:" + cartId; Map data = hashCommands.hgetall(key); + Map itemPrices = hashCommands.hgetall(priceKey); + String managerId = hashCommands.hget(key, "_metadata_eateryManagerId"); if (data.isEmpty()) { throw new GraphQLException("NotFoundCart"); } - List items = new ArrayList<>(); + Cart cart = new Cart(); + cart.Id = cartId; + cart.eateryId = UUID.fromString(data.get("_metadata_eatery")); + cart.userId = UUID.fromString(data.get("_metadata_owner")); + cart.items = new ArrayList<>(); - data.forEach((k, v) -> { - if (!k.startsWith("_")) { - items.add(new CartItem(k, Integer.parseInt(v))); - } - }); + long total = 0; - return new Cart( - cartId, - UUID.fromString(data.get("_metadata_owner")), - UUID.fromString(data.get("_metadata_eatery")), - items - ); + for (Map.Entry entry : data.entrySet()) { + String field = entry.getKey(); + if (field.startsWith("_metadata_")) continue; + + Integer qty = Integer.parseInt(entry.getValue()); + Double price = Double.parseDouble(itemPrices.getOrDefault(field, "0")); + Double subTotal = qty * price; + + CartItem item = new CartItem(); + item.productId = field; + item.quantity = qty; + item.priceAtTimeOfAdding = price; + item.subTotal = subTotal; + + cart.items.add(item); + total += subTotal; + } + cart.totalAmount = total; + + try { + Response response = managerController.getManagerPaymentQr( + UUID.fromString(managerId), + total, + cartId.toString() + ); + + cart.paymentQrUrl = response.readEntity(String.class); + } catch (Exception e) { + cart.paymentQrUrl = ""; + } + + return cart; } @Mutation("addItem") @@ -117,19 +155,36 @@ public class CartService { @Name("quantity") long quantity ) { String key = "cart:" + cartId; + String priceKey = "cart_prices:" + cartId; - UUID eateryId = UUID.fromString(hashCommands.hget(key, "_metadata_eatery")); + String eateryIdStr = hashCommands.hget(key, "_metadata_eatery"); + if (eateryIdStr == null) throw new RuntimeException("CartNotFound"); + + UUID eateryId = UUID.fromString(eateryIdStr); return eateryController .checkMenuItemExists(eateryId, menuItemId) .onItem() .transform(response -> { if (response.getStatus() != Response.Status.OK.getStatusCode()) { - throw new RuntimeException("NotFoundEatery"); + throw new RuntimeException("NotFoundMenuItemInEatery"); } + Double menuItemPrice = response.readEntity(Double.class); + hashCommands.hincrby(key, menuItemId.toString(), quantity); + + hashCommands.hset( + priceKey, + menuItemId.toString(), + String.valueOf(menuItemPrice) + ); + hashCommands.getDataSource().key().expire(key, Duration.ofDays(30)); + hashCommands + .getDataSource() + .key() + .expire(priceKey, Duration.ofDays(30)); try { return getCart(cartId); diff --git a/cart-service/src/main/resources/application.properties b/cart-service/src/main/resources/application.properties index 71ca8f2..78018a6 100644 --- a/cart-service/src/main/resources/application.properties +++ b/cart-service/src/main/resources/application.properties @@ -21,3 +21,4 @@ quarkus.redis.hosts=redis://${REDIS_SERVICE_SERVICE_HOST:localhost}:${REDIS_SERV # Rest Client quarkus.rest-client.eatery.url=http://eatery-service +quarkus.rest-client.account.url=http://account-service diff --git a/cart-service/src/test/java/com/drinkool/services/CartServiceTest.java b/cart-service/src/test/java/com/drinkool/services/CartServiceTest.java index 62c999b..daa25b1 100644 --- a/cart-service/src/test/java/com/drinkool/services/CartServiceTest.java +++ b/cart-service/src/test/java/com/drinkool/services/CartServiceTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; import com.drinkool.controllers.EateryController; +import com.drinkool.controllers.ManagerController; import com.drinkool.dtos.Cart; import io.quarkus.redis.datasource.RedisDataSource; import io.quarkus.redis.datasource.hash.HashCommands; @@ -40,6 +41,11 @@ public class CartServiceTest { @RestClient EateryController eateryController; + + @InjectMock + @RestClient + ManagerController managerController; + @Inject RedisDataSource redisDataSource; @@ -59,7 +65,7 @@ public class CartServiceTest { UUID eateryId = UUID.randomUUID(); when(eateryController.getEateryById(eateryId)).thenReturn( - Uni.createFrom().item(Response.ok().build()) + Uni.createFrom().item(Response.ok(UUID.randomUUID()).build()) ); String cartId = cartService @@ -129,7 +135,7 @@ public class CartServiceTest { hashCommands.hset(key, "_metadata_owner", UUID.randomUUID().toString()); when(eateryController.checkMenuItemExists(eateryId, menuItemId)).thenReturn( - Uni.createFrom().item(Response.ok().build()) + Uni.createFrom().item(Response.ok(17.0).build()) ); Cart result = cartService diff --git a/eatery-service/src/main/java/com/drinkool/services/EateryService.java b/eatery-service/src/main/java/com/drinkool/services/EateryService.java index d7627e1..0f3fd7c 100644 --- a/eatery-service/src/main/java/com/drinkool/services/EateryService.java +++ b/eatery-service/src/main/java/com/drinkool/services/EateryService.java @@ -42,33 +42,33 @@ public class EateryService { EateryEntity eatery = EateryEntity.findById(id); if (eatery == null) { - return Response.status(Response.Status.NOT_FOUND) + return Response.status(Response.Status.BAD_REQUEST) .entity("NotFoundEatery") .build(); } - return Response.ok().build(); + return Response.ok(eatery.ownerId.toString()).build(); } @GET @Path("/{eateryId}/{menuItemId}") - public Response checkMenuItemExists( + public Response getMenuItemPrice( @PathParam("eateryId") UUID eateryId, @PathParam("menuItemId") UUID menuItemId ) { - long count = MenuItemEntity.find( - "id = ?2 and eatery.id = ?1", - eateryId, - menuItemId - ).count(); + MenuItemEntity menuItem = MenuItemEntity.find( + "id = ?1 and eatery.id = ?2", + menuItemId, + eateryId + ).firstResult(); - if (count == 0) { - return Response.status(Response.Status.NOT_FOUND) + if (menuItem == null) { + return Response.status(Response.Status.BAD_REQUEST) .entity("NotFoundMenuItem") .build(); } - return Response.ok().build(); + return Response.ok(menuItem.price).build(); } @Incoming("manager-in") diff --git a/eatery-service/src/main/resources/application.properties b/eatery-service/src/main/resources/application.properties index f499263..953717c 100644 --- a/eatery-service/src/main/resources/application.properties +++ b/eatery-service/src/main/resources/application.properties @@ -28,10 +28,12 @@ quarkus.native.additional-build-args=--future-defaults=all # Kafka %prod.mp.messaging.connector.smallrye-kafka.bootstrap.servers=${KAFKA_SERVICE_SERVICE_HOST:host.docker.internal}:9092 -mp.messaging.incoming.manager-in.connector=smallrye-in-memory ## Create restaurant topic mp.messaging.incoming.manager-in.connector=smallrye-kafka mp.messaging.incoming.manager-in.topic=create-restaurant mp.messaging.incoming.manager-in.auto.offset.reset=earliest -quarkus.messaging.kafka.serializer-generation.enabled=true \ No newline at end of file +quarkus.messaging.kafka.serializer-generation.enabled=true + +# Rest Client +quarkus.rest-client.account.url=http://account-service diff --git a/file-service/pom.xml b/file-service/pom.xml index 7187409..c70ab81 100644 --- a/file-service/pom.xml +++ b/file-service/pom.xml @@ -71,7 +71,7 @@ - push-python-image + push quarkus.container-image.push @@ -86,7 +86,7 @@ docker-push-python - install + package exec diff --git a/lib/src/main/java/com/drinkool/lib/dtos/ManagerSignup.java b/lib/src/main/java/com/drinkool/lib/dtos/ManagerSignup.java index 89db905..57cbcb1 100644 --- a/lib/src/main/java/com/drinkool/lib/dtos/ManagerSignup.java +++ b/lib/src/main/java/com/drinkool/lib/dtos/ManagerSignup.java @@ -3,4 +3,6 @@ package com.drinkool.lib.dtos; public class ManagerSignup extends Signup { public String eateryName; + + public String bankAccount; }