payment_edge_test.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. ))
  34. require.NoError(t, db.Create(&dao.PaymentMethodConfig{
  35. Code: paymentService.PaymentCash, Name: "cash", InputMode: paymentService.InputModeManualAmount,
  36. Scenes: []string{paymentService.EntryCounter}, Enabled: true, IsSystem: true,
  37. }).Error)
  38. require.NoError(t, db.Create(&dao.PaymentEntryConfig{
  39. Code: paymentService.EntryCounter, Name: "counter", Enabled: true, IsSystem: true,
  40. }).Error)
  41. require.NoError(t, db.Create(&dao.PaymentEntryMethod{
  42. EntryCode: paymentService.EntryCounter, MethodCode: paymentService.PaymentCash,
  43. }).Error)
  44. return &VehicleService{}
  45. }
  46. func createVehiclePaymentFixture(t *testing.T, withTicket bool) (*dao.VehicleRecord, *dao.Vehicle) {
  47. t.Helper()
  48. vehicleType := &dao.VehicleType{Name: "temporary", IsSystem: true}
  49. require.NoError(t, global.GVA_DB.Create(vehicleType).Error)
  50. require.NoError(t, global.GVA_DB.Create(&dao.FeeConfig{
  51. VehicleTypeID: vehicleType.ID, StartTime: 0, StartFee: 0, UnitTime: 60, UnitFee: 1, DailyMaxFee: 100,
  52. }).Error)
  53. vehicle := &dao.Vehicle{PlateNumber: "PAY-EDGE", VehicleTypeID: vehicleType.ID}
  54. require.NoError(t, global.GVA_DB.Create(vehicle).Error)
  55. record := &dao.VehicleRecord{
  56. PlateNumber: vehicle.PlateNumber, EntryTime: time.Now().Add(-2 * time.Hour), PaymentStatus: "unpaid",
  57. }
  58. require.NoError(t, global.GVA_DB.Create(record).Error)
  59. if withTicket {
  60. _, err := ticketService.NewTicketService().CreateTx(global.GVA_DB, vehicle.PlateNumber, "test", record.ID)
  61. require.NoError(t, err)
  62. }
  63. return record, vehicle
  64. }
  65. func TestExitConfirmRollsBackPaymentWhenTicketUpdateFails(t *testing.T) {
  66. svc := setupVehiclePaymentTest(t)
  67. record, vehicle := createVehiclePaymentFixture(t, false)
  68. _, err := svc.ExitConfirm(request.ExitConfirmRequest{
  69. PlateNumber: vehicle.PlateNumber, PaymentEntry: paymentService.EntryCounter,
  70. PaymentMethod: paymentService.PaymentCash, PaidAmount: 10,
  71. }, 1)
  72. require.Error(t, err)
  73. var persistedRecord dao.VehicleRecord
  74. require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
  75. require.Nil(t, persistedRecord.ExitTime)
  76. require.Equal(t, "unpaid", persistedRecord.PaymentStatus)
  77. var paymentCount int64
  78. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  79. require.Zero(t, paymentCount)
  80. var persistedVehicle dao.Vehicle
  81. require.NoError(t, global.GVA_DB.First(&persistedVehicle, vehicle.ID).Error)
  82. require.Zero(t, persistedVehicle.TotalFee)
  83. require.Zero(t, persistedVehicle.TotalStayTime)
  84. }
  85. func TestExitConfirmRejectsDuplicatePayment(t *testing.T) {
  86. svc := setupVehiclePaymentTest(t)
  87. record, vehicle := createVehiclePaymentFixture(t, true)
  88. req := request.ExitConfirmRequest{
  89. PlateNumber: vehicle.PlateNumber, PaymentEntry: paymentService.EntryCounter,
  90. PaymentMethod: paymentService.PaymentCash, PaidAmount: 10,
  91. }
  92. _, err := svc.ExitConfirm(req, 1)
  93. require.NoError(t, err)
  94. _, err = svc.ExitConfirm(req, 1)
  95. require.Error(t, err)
  96. var paymentCount int64
  97. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  98. require.EqualValues(t, 1, paymentCount)
  99. var ticket dao.DigitalTicket
  100. require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
  101. require.Equal(t, ticketService.StateExited, ticket.State)
  102. }
  103. func TestPayDigitalTicketUpdatesPaymentAndVehicleRecordTogether(t *testing.T) {
  104. svc := setupVehiclePaymentTest(t)
  105. record, _ := createVehiclePaymentFixture(t, true)
  106. var ticket dao.DigitalTicket
  107. require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
  108. require.NoError(t, svc.PayDigitalTicket(
  109. ticket.TicketNo, paymentService.EntryCounter, paymentService.PaymentCash, "ORDER-UNIFIED-1", 10, 7,
  110. ))
  111. var persistedRecord dao.VehicleRecord
  112. require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
  113. require.Equal(t, "paid", persistedRecord.PaymentStatus)
  114. require.Equal(t, paymentService.EntryCounter, persistedRecord.PaymentEntry)
  115. require.Equal(t, paymentService.PaymentCash, persistedRecord.PaymentMethod)
  116. require.Positive(t, persistedRecord.Fee)
  117. require.Nil(t, persistedRecord.ExitTime)
  118. var payment dao.PaymentRecord
  119. require.NoError(t, global.GVA_DB.Where("record_id = ?", record.ID).First(&payment).Error)
  120. require.EqualValues(t, 7, payment.OperatorID)
  121. require.InDelta(t, persistedRecord.Fee, payment.Amount, 0.001)
  122. require.NoError(t, global.GVA_DB.First(&ticket, ticket.ID).Error)
  123. require.Equal(t, ticketService.StatePaid, ticket.State)
  124. require.Equal(t, "ORDER-UNIFIED-1", ticket.PaymentOrderNo)
  125. require.InDelta(t, payment.Amount, ticket.Fee, 0.001)
  126. }
  127. func TestExitConfirmConsumesPrepaidTicketWithoutSecondPayment(t *testing.T) {
  128. svc := setupVehiclePaymentTest(t)
  129. record, vehicle := createVehiclePaymentFixture(t, true)
  130. var ticket dao.DigitalTicket
  131. require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
  132. require.NoError(t, svc.PayDigitalTicket(
  133. ticket.TicketNo, paymentService.EntryCounter, paymentService.PaymentCash, "ORDER-UNIFIED-2", 10, 7,
  134. ))
  135. result, err := svc.ExitConfirm(request.ExitConfirmRequest{
  136. PlateNumber: vehicle.PlateNumber,
  137. // A device may still send its normal free-release request. A prepaid
  138. // ticket must consume the recorded payment instead of charging again.
  139. PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentFree,
  140. }, 9)
  141. require.NoError(t, err)
  142. require.Equal(t, paymentService.PaymentCash, result.PaymentMethod)
  143. require.Equal(t, paymentService.EntryCounter, result.PaymentEntry)
  144. var paymentCount int64
  145. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  146. require.EqualValues(t, 1, paymentCount)
  147. var persistedRecord dao.VehicleRecord
  148. require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
  149. require.NotNil(t, persistedRecord.ExitTime)
  150. require.Equal(t, "paid", persistedRecord.PaymentStatus)
  151. require.Equal(t, paymentService.PaymentCash, persistedRecord.PaymentMethod)
  152. require.NoError(t, global.GVA_DB.First(&ticket, ticket.ID).Error)
  153. require.Equal(t, ticketService.StateExited, ticket.State)
  154. }