fix(orders,wallet): implement full-stack wallet deduction in PostgreSQL and sync charity donation total in user profile
This commit is contained in:
parent
51b2f811fa
commit
377ec7fb34
@ -378,14 +378,35 @@
|
||||
{ "index": 1, "name": "add_pet_store_reset", "description": "Add reset method in usePetStore and call on logout", "status": "done" },
|
||||
{ "index": 2, "name": "enforce_auth_guard_on_pet_store", "description": "Enforce isLoggedIn check on getActivePet()", "status": "done" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "EPIC-10-TASK-01",
|
||||
"title": "Full-Stack Wallet Payment & Balance Deduction Persistence",
|
||||
"description": "Add paymentMethod to CreateOrderDto and OrdersService.create in NestJS backend to deduct order amount from user walletBalance in PostgreSQL and create withdrawal WalletTransaction record upon checkout.",
|
||||
"architectural_layer": "business_logic",
|
||||
"assigned_role": "05_dev_backend",
|
||||
"priority": "HIGH",
|
||||
"status": "completed",
|
||||
"max_files_allowed": 5,
|
||||
"estimated_minutes": 25,
|
||||
"dependency_task_ids": ["EPIC-09-TASK-03"],
|
||||
"acceptance_criteria": [
|
||||
"کسر واقعی و دقیق مبلغ سفارش از کیف پول کاربر در دیتابیس PostgreSQL هنگام پرداخت با کیف پول",
|
||||
"ثبت تراکنش کسر از کیف پول (withdrawal) در دیتابیس و بروزرسانی لحظهای پروفایل کاربر"
|
||||
],
|
||||
"sub_steps": [
|
||||
{ "index": 1, "name": "add_payment_method_dto", "description": "Add paymentMethod field to CreateOrderDto", "status": "done" },
|
||||
{ "index": 2, "name": "implement_wallet_deduction_in_orders_service", "description": "Deduct user walletBalance and create WalletTransaction in OrdersService.create", "status": "done" },
|
||||
{ "index": 3, "name": "wire_checkout_page_wallet_sync", "description": "Send paymentMethod in API call and trigger fetchProfile() in CheckoutPage.tsx", "status": "done" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"total": 19,
|
||||
"completed": 19,
|
||||
"total": 20,
|
||||
"completed": 20,
|
||||
"in_progress": 0,
|
||||
"pending": 0,
|
||||
"generated_at": "2026-07-26T20:35:00Z",
|
||||
"decomposition_pass": 7
|
||||
"generated_at": "2026-07-26T20:40:00Z",
|
||||
"decomposition_pass": 8
|
||||
}
|
||||
}
|
||||
@ -7,12 +7,12 @@
|
||||
"status": "COMPLETE",
|
||||
"checkpoint": {
|
||||
"active_agent": "01_auditor",
|
||||
"current_ticket_id": "EPIC-09-TASK-03",
|
||||
"current_ticket_id": "EPIC-10-TASK-01",
|
||||
"sub_step": {
|
||||
"index": 3,
|
||||
"total": 3,
|
||||
"name": "enforce_auth_guard_on_pet_store",
|
||||
"description": "Root cause pet profile mismatch resolved; Prisma Client regenerated, configurable charity round step added in admin panel, all builds 100% verified"
|
||||
"name": "wire_checkout_page_wallet_sync",
|
||||
"description": "Full-stack wallet balance deduction and charity donation total persistence in PostgreSQL complete and 100% verified"
|
||||
}
|
||||
},
|
||||
"review_phase": {
|
||||
|
||||
@ -39,6 +39,11 @@ export class CreateOrderDto {
|
||||
@IsOptional()
|
||||
isRefill?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'روش پرداخت (online, wallet)' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
paymentMethod?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'دوره زمانی تمدید خودکار (روز)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
|
||||
@ -116,8 +116,35 @@ export class OrdersService {
|
||||
const finalAmount = Math.max(totalAmount - discountAmount - refillDiscount + charityAmount, 0);
|
||||
const trackingNumber = this.generateTrackingNumber();
|
||||
|
||||
// Deduct user wallet balance if payment method is wallet
|
||||
if (createOrderDto.paymentMethod === 'wallet' && userId) {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new NotFoundException('کاربر یافت نشد');
|
||||
}
|
||||
const userBalance = Number(user.walletBalance || 0);
|
||||
if (userBalance < finalAmount) {
|
||||
throw new BadRequestException('موجودی کیف پول برای پرداخت این سفارش کافی نیست');
|
||||
}
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
walletBalance: { decrement: finalAmount }
|
||||
}
|
||||
});
|
||||
await this.prisma.walletTransaction.create({
|
||||
data: {
|
||||
userId,
|
||||
amount: finalAmount,
|
||||
type: 'withdrawal',
|
||||
status: 'completed',
|
||||
description: `پرداخت سفارش ${trackingNumber}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Increment user charity total if charity donation was added
|
||||
if (charityAmount > 0) {
|
||||
if (charityAmount > 0 && userId) {
|
||||
try {
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
|
||||
@ -114,19 +114,15 @@ export default function CheckoutPage() {
|
||||
items: [...items],
|
||||
total: totalAmount,
|
||||
charityDonation: charityDonation,
|
||||
paymentMethod: paymentMethod,
|
||||
isRefill: isSubscribed,
|
||||
petId: activePet?.id,
|
||||
shippingAddress: shippingAddressStr
|
||||
});
|
||||
|
||||
// 3. Update user charity total
|
||||
if (charityDonation > 0) {
|
||||
useUserStore.setState((state) => ({
|
||||
profile: {
|
||||
...state.profile,
|
||||
charityDonationTotal: (state.profile.charityDonationTotal || 0) + charityDonation
|
||||
}
|
||||
}));
|
||||
}
|
||||
// Re-fetch user profile from backend to sync PostgreSQL wallet balance and charity donation total
|
||||
const { fetchProfile } = useUserStore.getState();
|
||||
await fetchProfile();
|
||||
|
||||
// 4. Update pet consumptions and charity contributions for active pet
|
||||
if (activePet) {
|
||||
|
||||
@ -41,6 +41,7 @@ export class OrderService {
|
||||
couponCode?: string;
|
||||
prescriptionUrl?: string;
|
||||
charityDonation?: number;
|
||||
paymentMethod?: string;
|
||||
isRefill?: boolean;
|
||||
refillIntervalDays?: number;
|
||||
shippingAddress?: string;
|
||||
|
||||
@ -21,6 +21,8 @@ interface Order {
|
||||
items: CartItem[];
|
||||
total: number;
|
||||
charityDonation?: number;
|
||||
paymentMethod?: string;
|
||||
isRefill?: boolean;
|
||||
status: 'processing' | 'shipped' | 'delivered';
|
||||
petId?: string;
|
||||
shippingAddress?: string;
|
||||
@ -39,7 +41,7 @@ interface CartStore {
|
||||
toggleSubscription: () => void;
|
||||
setCharityDonation: (amount: number) => void;
|
||||
applyCoupon: (code: string) => Promise<boolean>;
|
||||
addOrder: (order: Omit<Order, 'id' | 'date' | 'status' | 'trackingNumber'>) => Promise<string>;
|
||||
addOrder: (orderData: Omit<Order, 'id' | 'date' | 'status' | 'trackingNumber'>) => Promise<string>;
|
||||
clearCart: () => void;
|
||||
removeCoupon: () => void;
|
||||
setOrders: (orders: any[]) => void;
|
||||
@ -104,6 +106,12 @@ export const useCartStore = create<CartStore>()(
|
||||
addOrder: async (orderData) => {
|
||||
const payload = {
|
||||
petId: orderData.petId,
|
||||
couponCode: get().coupon?.code,
|
||||
charityDonation: orderData.charityDonation,
|
||||
paymentMethod: orderData.paymentMethod,
|
||||
isRefill: orderData.isRefill || get().isSubscribed,
|
||||
refillIntervalDays: 60,
|
||||
shippingAddress: orderData.shippingAddress,
|
||||
items: orderData.items.map(i => ({
|
||||
productId: i.product.id,
|
||||
quantity: i.quantity
|
||||
|
||||
Loading…
Reference in New Issue
Block a user