Skip to content

Commit

Permalink
Merge pull request #544 from FoxComm/bug/fix-gift-card-capture
Browse files Browse the repository at this point in the history
Fix gift card capture and store credit
  • Loading branch information
Jeff Mataya authored Dec 7, 2016
2 parents 2fd852d + 1592221 commit 4888f37
Show file tree
Hide file tree
Showing 18 changed files with 245 additions and 189 deletions.
15 changes: 10 additions & 5 deletions phoenix-scala/app/failures/GiftCardFailures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,29 @@ object GiftCardFailures {

case class GiftCardPaymentAlreadyAdded(refNum: String, code: String) extends Failure {
override def description =
s"giftCard with code=$code already added as payment method to order with refNum=$refNum"
s"Gift Card with code=$code already added as payment method to order with refNum=$refNum"
}

case class GiftCardPaymentNotFound(refNum: String, code: String) extends Failure {
override def description =
s"giftCard with code=$code is not added as payment method to order with refNum=$refNum"
s"Gift Card with code=$code is not added as payment method to order with refNum=$refNum"
}

case class GiftCardAuthAdjustmentNotFound(orderPaymentId: Int) extends Failure {
override def description =
s"Cannot capture a Gift Card using order payment $orderPaymentId because no adjustment in auth"
}

case class GiftCardNotEnoughBalance(gc: GiftCard, requestedAmount: Int) extends Failure {
override def description =
s"giftCard with code=${gc.code} has availableBalance=${gc.availableBalance} less than requestedAmount=$requestedAmount"
s"Gift Card with code=${gc.code} has availableBalance=${gc.availableBalance} less than requestedAmount=$requestedAmount"
}

case class GiftCardIsInactive(gc: GiftCard) extends Failure {
override def description = s"giftCard with id=${gc.id} is inactive"
override def description = s"Gift Card with id=${gc.id} is inactive"
}

case object CreditCardMustHaveAddress extends Failure {
override def description = "cannot create creditCard without an address"
override def description = "cannot create Credit Card without an address"
}
}
5 changes: 5 additions & 0 deletions phoenix-scala/app/failures/StoreCreditFailures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ object StoreCreditFailures {
override def description =
s"customer with id=$id has storeCredit=$has less than requestedAmount=$want"
}

case class StoreCreditAuthAdjustmentNotFound(orderPaymentId: Int) extends Failure {
override def description =
s"Cannot capture a Store Credit using order payment $orderPaymentId because no adjustment in auth"
}
}
33 changes: 20 additions & 13 deletions phoenix-scala/app/models/payment/giftcard/GiftCard.scala
Original file line number Diff line number Diff line change
Expand Up @@ -265,25 +265,31 @@ object GiftCards

import GiftCard._

def auth(giftCard: GiftCard, orderPaymentId: Option[Int], debit: Int = 0, credit: Int = 0)(
def auth(giftCard: GiftCard, orderPaymentId: Int, debit: Int = 0)(
implicit ec: EC): DbResultT[GiftCardAdjustment] =
adjust(giftCard, orderPaymentId, debit = debit, credit = credit, state = Adj.Auth)
adjust(giftCard, orderPaymentId.some, debit = debit, state = Adj.Auth)

def authOrderPayment(
giftCard: GiftCard,
pmt: OrderPayment,
maxPaymentAmount: Option[Int] = None)(implicit ec: EC): DbResultT[GiftCardAdjustment] =
auth(giftCard = giftCard,
orderPaymentId = pmt.id.some,
debit = pmt.getAmount(maxPaymentAmount))
auth(giftCard = giftCard, orderPaymentId = pmt.id, debit = pmt.getAmount(maxPaymentAmount))

def captureOrderPayment(giftCard: GiftCard, pmt: OrderPayment, maxAmount: Option[Int] = None)(
implicit ec: EC): DbResultT[GiftCardAdjustment] =
capture(giftCard, pmt.id.some, debit = pmt.getAmount(maxAmount))
capture(giftCard, pmt.id, debit = pmt.getAmount(maxAmount))

def capture(giftCard: GiftCard, orderPaymentId: Option[Int], debit: Int, credit: Int = 0)(
def capture(giftCard: GiftCard, orderPaymentId: Int, debit: Int)(
implicit ec: EC): DbResultT[GiftCardAdjustment] =
adjust(giftCard, orderPaymentId, debit = debit, credit = credit, state = Adj.Capture)
for {
auth * <~ GiftCardAdjustments
.authorizedOrderPayment(orderPaymentId)
.mustFindOneOr(GiftCardAuthAdjustmentNotFound(orderPaymentId))
_ * <~ (
require(debit <= auth.debit)
)
cap * <~ GiftCardAdjustments.update(auth, auth.copy(debit = debit, state = Adj.Capture))
} yield cap

def cancelByCsr(giftCard: GiftCard, storeAdmin: User)(
implicit ec: EC): DbResultT[GiftCardAdjustment] = {
Expand Down Expand Up @@ -321,11 +327,12 @@ object GiftCards
def findActive(): QuerySeq =
filter(_.state === (GiftCard.Active: GiftCard.State))

private def adjust(giftCard: GiftCard,
orderPaymentId: Option[Int],
debit: Int = 0,
credit: Int = 0,
state: GiftCardAdjustment.State = Adj.Auth)(
//Public because tests use it to validate DB constraints
def adjust(giftCard: GiftCard,
orderPaymentId: Option[Int],
debit: Int = 0,
credit: Int = 0,
state: GiftCardAdjustment.State = Adj.Auth)(
implicit ec: EC): DbResultT[GiftCardAdjustment] = {
val balance = giftCard.availableBalance - debit + credit
val adjustment = Adj(giftCardId = giftCard.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ object GiftCardAdjustments
def authorizedOrderPayments(orderPaymentIds: Seq[Int]): QuerySeq =
filter(adj adj.orderPaymentId.inSet(orderPaymentIds) && adj.state === (Auth: State))

def authorizedOrderPayment(orderPaymentId: Int): QuerySeq =
filter(adj adj.orderPaymentId === orderPaymentId && adj.state === (Auth: State))

object scope {

implicit class GCAQuerySeqAdditions(query: QuerySeq) {
Expand Down
26 changes: 16 additions & 10 deletions phoenix-scala/app/models/payment/storecredit/StoreCredit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cats.data.Validated._
import cats.data.{ValidatedNel, Xor}
import cats.implicits._
import com.pellucid.sealerate
import failures.{Failure, Failures, GeneralFailure}
import failures.{Failure, Failures, GeneralFailure, StoreCreditFailures}
import models.account._
import models.cord.OrderPayment
import models.payment.PaymentMethod
Expand Down Expand Up @@ -202,10 +202,10 @@ class StoreCredits(tag: Tag) extends FoxTable[StoreCredit](tag, "store_credits")

object StoreCredits extends FoxTableQuery[StoreCredit, StoreCredits](new StoreCredits(_)) {

def auth(storeCredit: StoreCredit, orderPaymentId: Option[Int], amount: Int = 0)(
def auth(storeCredit: StoreCredit, orderPaymentId: Int, amount: Int = 0)(
implicit ec: EC): DbResultT[StoreCreditAdjustment] =
debit(storeCredit = storeCredit,
orderPaymentId = orderPaymentId,
orderPaymentId = orderPaymentId.some,
amount = amount,
state = Adj.Auth)

Expand All @@ -214,23 +214,29 @@ object StoreCredits extends FoxTableQuery[StoreCredit, StoreCredits](new StoreCr
pmt: OrderPayment,
maxPaymentAmount: Option[Int] = None)(implicit ec: EC): DbResultT[StoreCreditAdjustment] =
auth(storeCredit = storeCredit,
orderPaymentId = pmt.id.some,
orderPaymentId = pmt.id,
amount = pmt.getAmount(maxPaymentAmount))

def captureOrderPayment(
storeCredit: StoreCredit,
pmt: OrderPayment,
maxPaymentAmount: Option[Int] = None)(implicit ec: EC): DbResultT[StoreCreditAdjustment] =
capture(storeCredit = storeCredit,
orderPaymentId = pmt.id.some,
orderPaymentId = pmt.id,
amount = pmt.getAmount(maxPaymentAmount))

def capture(storeCredit: StoreCredit, orderPaymentId: Option[Int], amount: Int = 0)(
def capture(storeCredit: StoreCredit, orderPaymentId: Int, amount: Int)(
implicit ec: EC): DbResultT[StoreCreditAdjustment] =
debit(storeCredit = storeCredit,
orderPaymentId = orderPaymentId,
amount = amount,
state = Adj.Capture)
for {
auth * <~ StoreCreditAdjustments
.authorizedOrderPayment(orderPaymentId)
.mustFindOneOr(StoreCreditFailures.StoreCreditAuthAdjustmentNotFound(orderPaymentId))
_ * <~ (
require(amount <= auth.debit)
)
cap * <~ StoreCreditAdjustments.update(auth,
auth.copy(debit = amount, state = Adj.Capture))
} yield cap

def cancelByCsr(storeCredit: StoreCredit, storeAdmin: User)(
implicit ec: EC): DbResultT[StoreCreditAdjustment] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ object StoreCreditAdjustments
def authorizedOrderPayments(orderPaymentIds: Seq[Int]): QuerySeq =
filter(adj adj.orderPaymentId.inSet(orderPaymentIds) && adj.state === (Auth: State))

def authorizedOrderPayment(orderPaymentId: Int): QuerySeq =
filter(adj adj.orderPaymentId === orderPaymentId && adj.state === (Auth: State))

object scope {

implicit class SCAQuerySeqAdditions(query: QuerySeq) {
Expand Down
2 changes: 1 addition & 1 deletion phoenix-scala/app/services/Capture.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ case class Capture(payload: CapturePayloads.Capture)(implicit ec: EC, db: DB, ap
scPayments,
order.currency)
internalCaptureTotal = total - externalCaptureTotal
_ * <~ externalCapture(externalCaptureTotal, order)
_ * <~ internalCapture(internalCaptureTotal, order, customer, gcPayments, scPayments)
_ * <~ externalCapture(externalCaptureTotal, order)

resp = CaptureResponse(order = order.refNum,
captured = total,
Expand Down
78 changes: 38 additions & 40 deletions phoenix-scala/app/services/orders/OrderStateUpdater.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object OrderStateUpdater {
admin: User,
refNumbers: Seq[String],
newState: Order.State,
skipActivity: Boolean = false)(implicit ec: EC, ac: AC): DbResultT[BatchMetadata] = {
skipActivity: Boolean = false)(implicit ec: EC, ac: AC, db: DB): DbResultT[BatchMetadata] = {

val query = Orders.filter(_.referenceNumber.inSet(refNumbers)).result
appendForUpdate(query).dbresult.flatMap { orders
Expand All @@ -60,67 +60,65 @@ object OrderStateUpdater {
val possibleRefNums = validTransitions.map(_.referenceNumber)
val skipActivityMod = skipActivity || possibleRefNums.isEmpty

updateQueriesWrapper(admin, possibleRefNums, newState, skipActivityMod).dbresult.flatMap {
_
// Failure handling
val invalid = invalidTransitions.map(o
(o.refNum, StateTransitionNotAllowed(o.state, newState, o.refNum).description))
val notFound = refNumbers
.filterNot(refNum orders.map(_.referenceNumber).contains(refNum))
.map(refNum (refNum, NotFoundFailure400(Order, refNum).description))

val batchFailures = (invalid ++ notFound).toMap
DbResultT.good(BatchMetadata(BatchMetadataSource(Order, possibleRefNums, batchFailures)))
updateQueriesWrapper(admin, possibleRefNums, newState, skipActivityMod).flatMap { _
// Failure handling
val invalid = invalidTransitions.map(o
(o.refNum, StateTransitionNotAllowed(o.state, newState, o.refNum).description))
val notFound = refNumbers
.filterNot(refNum orders.map(_.referenceNumber).contains(refNum))
.map(refNum (refNum, NotFoundFailure400(Order, refNum).description))

val batchFailures = (invalid ++ notFound).toMap
DbResultT.good(BatchMetadata(BatchMetadataSource(Order, possibleRefNums, batchFailures)))
}
}
}

private def updateQueriesWrapper(admin: User,
cordRefs: Seq[String],
newState: State,
skipActivity: Boolean = false)(implicit ec: EC, ac: AC) = {
private def updateQueriesWrapper(
admin: User,
cordRefs: Seq[String],
newState: State,
skipActivity: Boolean = false)(implicit ec: EC, ac: AC, db: DB) = {

if (skipActivity)
updateQueries(admin, cordRefs, newState)
else
orderBulkStateChanged(newState, cordRefs, admin.some).value >>
updateQueries(admin, cordRefs, newState)
for {
_ * <~ orderBulkStateChanged(newState, cordRefs, admin.some).value
_ * <~ updateQueries(admin, cordRefs, newState)
} yield ()
}

private def updateQueries(admin: User, cordRefs: Seq[String], newState: State)(implicit ec: EC) =
private def updateQueries(admin: User, cordRefs: Seq[String], newState: State)(implicit ec: EC,
db: DB) =
newState match {
case Canceled
cancelOrders(cordRefs)
case _
Orders.filter(_.referenceNumber.inSet(cordRefs)).map(_.state).update(newState)
for {
_ * <~ Orders.filter(_.referenceNumber.inSet(cordRefs)).map(_.state).update(newState)
} yield ()
}

private def cancelOrders(cordRefs: Seq[String])(implicit ec: EC) = {
val updateLineItems = OrderLineItems
.filter(_.cordRef.inSetBind(cordRefs))
.map(_.state)
.update(OrderLineItem.Canceled)

val cancelPayments = for {
orderPayments OrderPayments.filter(_.cordRef.inSetBind(cordRefs)).result
_ cancelGiftCards(orderPayments)
_ cancelStoreCredits(orderPayments)
// TODO: add credit card charge return
private def cancelOrders(cordRefs: Seq[String])(implicit ec: EC, db: DB) =
for {
updateLineItems * <~ OrderLineItems
.filter(_.cordRef.inSetBind(cordRefs))
.map(_.state)
.update(OrderLineItem.Canceled)

orderPayments * <~ OrderPayments.filter(_.cordRef.inSetBind(cordRefs)).result
_ * <~ cancelGiftCards(orderPayments)
_ * <~ cancelStoreCredits(orderPayments)
_ * <~ Orders.filter(_.referenceNumber.inSetBind(cordRefs)).map(_.state).update(Canceled)
} yield ()

val updateOrder =
Orders.filter(_.referenceNumber.inSetBind(cordRefs)).map(_.state).update(Canceled)

// (updateLineItems >> updateOrderPayments >> updateOrder).transactionally
(updateLineItems >> updateOrder >> cancelPayments).transactionally
}

private def cancelGiftCards(orderPayments: Seq[OrderPayment]) = {
private def cancelGiftCards(orderPayments: Seq[OrderPayment])(implicit ec: EC, db: DB) = {
val paymentIds = orderPayments.map(_.id)
GiftCardAdjustments.filter(_.orderPaymentId.inSetBind(paymentIds)).cancel()
}

private def cancelStoreCredits(orderPayments: Seq[OrderPayment]) = {
private def cancelStoreCredits(orderPayments: Seq[OrderPayment])(implicit ec: EC, db: DB) = {
val paymentIds = orderPayments.map(_.id)
StoreCreditAdjustments.filter(_.orderPaymentId.inSetBind(paymentIds)).cancel()
}
Expand Down
2 changes: 0 additions & 2 deletions phoenix-scala/app/utils/seeds/GiftCardSeeds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ trait GiftCardSeeds {
_ * <~ GiftCardSubtypes.createAll(giftCardSubTypes)
origin * <~ GiftCardManuals.create(GiftCardManual(adminId = 1, reasonId = 1))
gc1 * <~ GiftCards.create(giftCard.copy(originId = origin.id))
_ * <~ GiftCards.capture(gc1, debit = 1000, orderPaymentId = None)

gc2 * <~ GiftCards.create(
build(payload(balance = 10000, reasonId = 1), originId = origin.id))
_ * <~ Notes.createAll(giftCardNotes.map(_.copy(referenceId = gc1.id)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ trait OrderGenerator extends ShipmentSeeds {
StoreCredit(originId = origin.id, accountId = accountId, originalBalance = totals))
op * <~ OrderPayments.create(
OrderPayment.build(sc).copy(cordRef = cart.refNum, amount = totals.some))
_ * <~ StoreCredits.capture(sc, op.id.some, totals)
_ * <~ StoreCredits.auth(sc, op.id, totals)
_ * <~ StoreCredits.capture(sc, op.id, totals)
addr * <~ getDefaultAddress(accountId)
shipMethodIds * <~ ShippingMethods.map(_.id).result
shipMethod * <~ getShipMethod(1 + Random.nextInt(shipMethodIds.length))
Expand Down
Loading

0 comments on commit 4888f37

Please sign in to comment.