Skip to content

Commit 57eb17b

Browse files
committed
feat: Add force delete command for expired and abandoned checkouts
1 parent 62679c6 commit 57eb17b

6 files changed

Lines changed: 142 additions & 11 deletions

File tree

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,6 @@ mod-tidy: ## Tidy Go modules
129129
# Maintenance commands
130130
expire-checkouts: ## Expire old checkouts manually
131131
go run ./cmd/expire-checkouts
132+
133+
force-delete-checkouts: ## Force delete all expired, abandoned, and old completed checkouts
134+
go run ./cmd/expire-checkouts -force

cmd/expire-checkouts/main.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"flag"
45
"log"
56

67
"github.com/joho/godotenv"
@@ -11,14 +12,22 @@ import (
1112
)
1213

1314
func main() {
15+
// Parse command line flags
16+
forceDelete := flag.Bool("force", false, "Force delete all expired, abandoned, and old completed checkouts")
17+
flag.Parse()
18+
1419
// Load environment variables
1520
if err := godotenv.Load(); err != nil {
1621
log.Println("No .env file found, using environment variables")
1722
}
1823

1924
// Initialize logger
2025
logger := logger.NewLogger()
21-
logger.Info("Starting checkout expiry cleanup tool")
26+
if *forceDelete {
27+
logger.Info("Starting checkout force deletion tool")
28+
} else {
29+
logger.Info("Starting checkout expiry cleanup tool")
30+
}
2231

2332
// Load configuration
2433
cfg, err := config.LoadConfig()
@@ -39,15 +48,27 @@ func main() {
3948
// Get checkout use case
4049
checkoutUseCase := diContainer.UseCases().CheckoutUseCase()
4150

42-
// Expire old checkouts
43-
result, err := checkoutUseCase.ExpireOldCheckouts()
44-
if err != nil {
45-
logger.Fatal("Failed to expire old checkouts: %v", err)
46-
}
51+
if *forceDelete {
52+
// Force delete all expired checkouts
53+
deleteResult, err := checkoutUseCase.ForceDeleteAllExpiredCheckouts()
54+
if err != nil {
55+
logger.Fatal("Failed to force delete expired checkouts: %v", err)
56+
}
57+
58+
logger.Info("Force deletion completed:")
59+
logger.Info("- Force deleted checkouts: %d", deleteResult.DeletedCount)
60+
logger.Info("Total processed: %d", deleteResult.DeletedCount)
61+
} else {
62+
// Regular expire old checkouts
63+
expireResult, err := checkoutUseCase.ExpireOldCheckouts()
64+
if err != nil {
65+
logger.Fatal("Failed to expire old checkouts: %v", err)
66+
}
4767

48-
logger.Info("Checkout cleanup completed:")
49-
logger.Info("- Abandoned checkouts: %d", result.AbandonedCount)
50-
logger.Info("- Deleted checkouts: %d", result.DeletedCount)
51-
logger.Info("- Expired checkouts: %d", result.ExpiredCount)
52-
logger.Info("Total processed: %d", result.AbandonedCount+result.DeletedCount+result.ExpiredCount)
68+
logger.Info("Checkout cleanup completed:")
69+
logger.Info("- Abandoned checkouts: %d", expireResult.AbandonedCount)
70+
logger.Info("- Deleted checkouts: %d", expireResult.DeletedCount)
71+
logger.Info("- Expired checkouts: %d", expireResult.ExpiredCount)
72+
logger.Info("Total processed: %d", expireResult.AbandonedCount+expireResult.DeletedCount+expireResult.ExpiredCount)
73+
}
5374
}

internal/application/usecase/checkout_usecase.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,28 @@ func (uc *CheckoutUseCase) ExpireOldCheckoutsLegacy() (int, error) {
572572
return result.AbandonedCount + result.DeletedCount + result.ExpiredCount, nil
573573
}
574574

575+
// ForceDeleteAllExpiredCheckouts forcefully deletes all expired, abandoned, and old completed checkouts
576+
func (uc *CheckoutUseCase) ForceDeleteAllExpiredCheckouts() (*CheckoutCleanupResult, error) {
577+
result := &CheckoutCleanupResult{}
578+
579+
// Get all expired checkouts for deletion
580+
checkoutsToDelete, err := uc.checkoutRepo.GetAllExpiredCheckoutsForDeletion()
581+
if err != nil {
582+
return result, fmt.Errorf("failed to get expired checkouts for deletion: %w", err)
583+
}
584+
585+
for _, checkout := range checkoutsToDelete {
586+
err = uc.checkoutRepo.Delete(checkout.ID)
587+
if err != nil {
588+
log.Printf("Failed to force delete checkout %d: %v", checkout.ID, err)
589+
continue
590+
}
591+
result.DeletedCount++
592+
}
593+
594+
return result, nil
595+
}
596+
575597
// CreateOrderFromCheckout creates an order from a checkout
576598
func (uc *CheckoutUseCase) CreateOrderFromCheckout(checkoutID uint) (*entity.Order, error) {
577599
// Get checkout

internal/domain/repository/checkout_repository.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,7 @@ type CheckoutRepository interface {
4848

4949
// HasActiveCheckoutsWithProduct checks if a product has any active checkouts
5050
HasActiveCheckoutsWithProduct(productID uint) (bool, error)
51+
52+
// GetAllExpiredCheckoutsForDeletion retrieves all expired checkouts for force deletion
53+
GetAllExpiredCheckoutsForDeletion() ([]*entity.Checkout, error)
5154
}

internal/infrastructure/repository/gorm/checkout_repository.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,28 @@ func (c *CheckoutRepository) Update(checkout *entity.Checkout) error {
249249
})
250250
}
251251

252+
// GetAllExpiredCheckoutsForDeletion implements repository.CheckoutRepository.
253+
func (c *CheckoutRepository) GetAllExpiredCheckoutsForDeletion() ([]*entity.Checkout, error) {
254+
var checkouts []*entity.Checkout
255+
256+
// Get all checkouts that are expired, abandoned, or completed (older than 30 days to be safe)
257+
thirtyDaysAgo := time.Now().AddDate(0, 0, -30)
258+
259+
err := c.db.Preload("Items").Preload("Items.Product").Preload("Items.ProductVariant").
260+
Where("status IN (?, ?, ?) OR updated_at < ?",
261+
entity.CheckoutStatusExpired,
262+
entity.CheckoutStatusAbandoned,
263+
entity.CheckoutStatusCompleted,
264+
thirtyDaysAgo).
265+
Find(&checkouts).Error
266+
267+
if err != nil {
268+
return nil, fmt.Errorf("failed to get expired checkouts for deletion: %w", err)
269+
}
270+
271+
return checkouts, nil
272+
}
273+
252274
// NewCheckoutRepository creates a new GORM-based CheckoutRepository
253275
func NewCheckoutRepository(db *gorm.DB) repository.CheckoutRepository {
254276
return &CheckoutRepository{db: db}

readme.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,3 +481,63 @@ Use Stripe's test cards for development:
481481
- `4000 0000 0000 9995` - Payment declined
482482

483483
For more test card numbers, visit [Stripe's testing documentation](https://stripe.com/docs/testing).
484+
485+
## Maintenance Commands
486+
487+
The project includes useful maintenance commands for database cleanup and optimization:
488+
489+
### Checkout Cleanup
490+
491+
The system provides two modes for managing expired and old checkouts:
492+
493+
```bash
494+
# Regular cleanup (recommended for scheduled runs)
495+
make expire-checkouts
496+
# or
497+
go run ./cmd/expire-checkouts
498+
```
499+
500+
This command performs the following operations:
501+
502+
- Marks checkouts with customer/shipping info as **abandoned** after 15 minutes of inactivity
503+
- **Deletes** empty checkouts older than 24 hours
504+
- **Deletes** abandoned checkouts older than 7 days
505+
- Marks remaining expired checkouts as **expired** (legacy support)
506+
507+
```bash
508+
# Force deletion (use with caution)
509+
make force-delete-checkouts
510+
# or
511+
go run ./cmd/expire-checkouts -force
512+
```
513+
514+
This command performs aggressive cleanup:
515+
516+
- **Force deletes** all expired, abandoned, and completed checkouts
517+
- **Force deletes** checkouts older than 30 days regardless of status
518+
- Should be used carefully as it permanently removes checkout data
519+
520+
### Usage Examples
521+
522+
```bash
523+
# Show help and available options
524+
./bin/expire-checkouts --help
525+
526+
# Regular cleanup (safe for automation)
527+
./bin/expire-checkouts
528+
529+
# Force delete all expired checkouts
530+
./bin/expire-checkouts -force
531+
```
532+
533+
### Scheduling Maintenance
534+
535+
For production environments, consider scheduling regular cleanup:
536+
537+
```bash
538+
# Example crontab entry (runs every hour)
539+
0 * * * * /path/to/commercify/bin/expire-checkouts
540+
541+
# Example crontab entry for weekly force cleanup (use with caution)
542+
0 2 * * 0 /path/to/commercify/bin/expire-checkouts -force
543+
```

0 commit comments

Comments
 (0)