payment_edge_test.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package vehicle
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. "github.com/glebarez/sqlite"
  7. "github.com/stretchr/testify/require"
  8. "gorm.io/gorm"
  9. "wails-app/internal/dao"
  10. "wails-app/internal/global"
  11. "wails-app/internal/model/vehicle/request"
  12. ticketService "wails-app/internal/modules/digital-ticket/service"
  13. paymentService "wails-app/internal/modules/payment/service"
  14. )
  15. func setupVehiclePaymentTest(t *testing.T) *VehicleService {
  16. t.Helper()
  17. dsn := fmt.Sprintf("file:vehicle-payment-%d?mode=memory&cache=shared", time.Now().UnixNano())
  18. db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
  19. require.NoError(t, err)
  20. sqlDB, err := db.DB()
  21. require.NoError(t, err)
  22. sqlDB.SetMaxOpenConns(8)
  23. t.Cleanup(func() {
  24. _ = sqlDB.Close()
  25. if global.GVA_DB == db {
  26. global.GVA_DB = nil
  27. }
  28. })
  29. global.GVA_DB = db
  30. require.NoError(t, db.AutoMigrate(
  31. &dao.Owner{}, &dao.Vehicle{}, &dao.VehicleType{}, &dao.FeeConfig{}, &dao.VehicleRecord{}, &dao.DigitalTicket{},
  32. &dao.PaymentMethodConfig{}, &dao.PaymentEntryConfig{}, &dao.PaymentEntryMethod{}, &dao.PaymentRecord{}, &dao.Shortlist{},
  33. &dao.Channel{}, &dao.UHFReader{}, &dao.ParkingLot{}, &dao.MonthlyCard{},
  34. ))
  35. require.NoError(t, db.Create(&dao.ParkingLot{
  36. LotCode: "TEST-LOT", LotName: "测试停车场", Capacity: 100, Available: 100,
  37. AllowMonthlyWhenFull: true, AllowVIPWhenFull: true,
  38. }).Error)
  39. require.NoError(t, db.Create(&dao.PaymentMethodConfig{
  40. Code: paymentService.PaymentCash, Name: "cash", InputMode: paymentService.InputModeManualAmount,
  41. Scenes: []string{paymentService.EntryCounter}, Enabled: true, IsSystem: true,
  42. }).Error)
  43. require.NoError(t, db.Create(&dao.PaymentEntryConfig{
  44. Code: paymentService.EntryCounter, Name: "counter", Enabled: true, IsSystem: true,
  45. }).Error)
  46. require.NoError(t, db.Create(&dao.PaymentEntryMethod{
  47. EntryCode: paymentService.EntryCounter, MethodCode: paymentService.PaymentCash,
  48. }).Error)
  49. return &VehicleService{}
  50. }
  51. func createVehiclePaymentFixture(t *testing.T, withTicket bool) (*dao.VehicleRecord, *dao.Vehicle) {
  52. t.Helper()
  53. vehicleType := &dao.VehicleType{Name: "temporary", IsSystem: true}
  54. require.NoError(t, global.GVA_DB.Create(vehicleType).Error)
  55. require.NoError(t, global.GVA_DB.Create(&dao.FeeConfig{
  56. VehicleTypeID: vehicleType.ID, StartTime: 0, StartFee: 0, UnitTime: 60, UnitFee: 1, DailyMaxFee: 100,
  57. }).Error)
  58. vehicle := &dao.Vehicle{PlateNumber: "PAY-EDGE", VehicleTypeID: vehicleType.ID}
  59. require.NoError(t, global.GVA_DB.Create(vehicle).Error)
  60. record := &dao.VehicleRecord{
  61. PlateNumber: vehicle.PlateNumber, EntryTime: time.Now().Add(-2 * time.Hour), PaymentStatus: "unpaid",
  62. }
  63. require.NoError(t, global.GVA_DB.Create(record).Error)
  64. if withTicket {
  65. _, err := ticketService.NewTicketService().CreateTx(global.GVA_DB, vehicle.PlateNumber, "test", record.ID)
  66. require.NoError(t, err)
  67. }
  68. return record, vehicle
  69. }
  70. func TestExitConfirmRollsBackPaymentWhenTicketUpdateFails(t *testing.T) {
  71. svc := setupVehiclePaymentTest(t)
  72. record, vehicle := createVehiclePaymentFixture(t, false)
  73. _, err := svc.ExitConfirm(request.ExitConfirmRequest{
  74. PlateNumber: vehicle.PlateNumber, PaymentEntry: paymentService.EntryCounter,
  75. PaymentMethod: paymentService.PaymentCash, PaidAmount: 10,
  76. }, 1)
  77. require.Error(t, err)
  78. var persistedRecord dao.VehicleRecord
  79. require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
  80. require.Nil(t, persistedRecord.ExitTime)
  81. require.Equal(t, "unpaid", persistedRecord.PaymentStatus)
  82. var paymentCount int64
  83. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  84. require.Zero(t, paymentCount)
  85. var persistedVehicle dao.Vehicle
  86. require.NoError(t, global.GVA_DB.First(&persistedVehicle, vehicle.ID).Error)
  87. require.Zero(t, persistedVehicle.TotalFee)
  88. require.Zero(t, persistedVehicle.TotalStayTime)
  89. }
  90. func TestExitConfirmRejectsDuplicatePayment(t *testing.T) {
  91. svc := setupVehiclePaymentTest(t)
  92. record, vehicle := createVehiclePaymentFixture(t, true)
  93. req := request.ExitConfirmRequest{
  94. PlateNumber: vehicle.PlateNumber, PaymentEntry: paymentService.EntryCounter,
  95. PaymentMethod: paymentService.PaymentCash, PaidAmount: 10,
  96. }
  97. _, err := svc.ExitConfirm(req, 1)
  98. require.NoError(t, err)
  99. _, err = svc.ExitConfirm(req, 1)
  100. require.Error(t, err)
  101. var paymentCount int64
  102. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  103. require.EqualValues(t, 1, paymentCount)
  104. var ticket dao.DigitalTicket
  105. require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
  106. require.Equal(t, ticketService.StateExited, ticket.State)
  107. }
  108. func TestPayDigitalTicketUpdatesPaymentAndVehicleRecordTogether(t *testing.T) {
  109. svc := setupVehiclePaymentTest(t)
  110. record, _ := createVehiclePaymentFixture(t, true)
  111. var ticket dao.DigitalTicket
  112. require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
  113. require.NoError(t, svc.PayDigitalTicket(
  114. ticket.TicketNo, paymentService.EntryCounter, paymentService.PaymentCash, "ORDER-UNIFIED-1", 10, 7,
  115. ))
  116. var persistedRecord dao.VehicleRecord
  117. require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
  118. require.Equal(t, "paid", persistedRecord.PaymentStatus)
  119. require.Equal(t, paymentService.EntryCounter, persistedRecord.PaymentEntry)
  120. require.Equal(t, paymentService.PaymentCash, persistedRecord.PaymentMethod)
  121. require.Positive(t, persistedRecord.Fee)
  122. require.Nil(t, persistedRecord.ExitTime)
  123. var payment dao.PaymentRecord
  124. require.NoError(t, global.GVA_DB.Where("record_id = ?", record.ID).First(&payment).Error)
  125. require.EqualValues(t, 7, payment.OperatorID)
  126. require.InDelta(t, persistedRecord.Fee, payment.Amount, 0.001)
  127. require.NoError(t, global.GVA_DB.First(&ticket, ticket.ID).Error)
  128. require.Equal(t, ticketService.StatePaid, ticket.State)
  129. require.Equal(t, "ORDER-UNIFIED-1", ticket.PaymentOrderNo)
  130. require.InDelta(t, payment.Amount, ticket.Fee, 0.001)
  131. }
  132. func TestExitConfirmConsumesPrepaidTicketWithoutSecondPayment(t *testing.T) {
  133. svc := setupVehiclePaymentTest(t)
  134. record, vehicle := createVehiclePaymentFixture(t, true)
  135. var ticket dao.DigitalTicket
  136. require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
  137. require.NoError(t, svc.PayDigitalTicket(
  138. ticket.TicketNo, paymentService.EntryCounter, paymentService.PaymentCash, "ORDER-UNIFIED-2", 10, 7,
  139. ))
  140. result, err := svc.ExitConfirm(request.ExitConfirmRequest{
  141. PlateNumber: vehicle.PlateNumber,
  142. // A device may still send its normal free-release request. A prepaid
  143. // ticket must consume the recorded payment instead of charging again.
  144. PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentFree,
  145. }, 9)
  146. require.NoError(t, err)
  147. require.Equal(t, paymentService.PaymentCash, result.PaymentMethod)
  148. require.Equal(t, paymentService.EntryCounter, result.PaymentEntry)
  149. var paymentCount int64
  150. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  151. require.EqualValues(t, 1, paymentCount)
  152. var persistedRecord dao.VehicleRecord
  153. require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
  154. require.NotNil(t, persistedRecord.ExitTime)
  155. require.Equal(t, "paid", persistedRecord.PaymentStatus)
  156. require.Equal(t, paymentService.PaymentCash, persistedRecord.PaymentMethod)
  157. require.NoError(t, global.GVA_DB.First(&ticket, ticket.ID).Error)
  158. require.Equal(t, ticketService.StateExited, ticket.State)
  159. }