-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Free up coupons from cancelled orders #2311
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,20 +9,31 @@ import objectframework.models.ObjectContext | |
import org.json4s.JsonAST._ | ||
import phoenix.failures.CartFailures.OrderAlreadyPlaced | ||
import phoenix.failures.CouponFailures.CouponIsNotActive | ||
import phoenix.models.cord.{Carts, Orders} | ||
import phoenix.models.coupon.Coupon | ||
import phoenix.models.cord.{Carts, Order, Orders} | ||
import phoenix.models.coupon.{Coupon, CouponUsageRules} | ||
import phoenix.models.traits.IlluminatedModel | ||
import phoenix.payloads.CartPayloads.CreateCart | ||
import phoenix.payloads.LineItemPayloads.UpdateLineItemsPayload | ||
import phoenix.responses.CouponResponses.CouponResponse | ||
import phoenix.responses.cord.CartResponse | ||
import phoenix.responses.cord.{CartResponse, OrderResponse} | ||
import phoenix.utils.time.RichInstant | ||
import testutils._ | ||
import testutils.apis.PhoenixAdminApi | ||
import testutils.fixtures.BakedFixtures | ||
import testutils.fixtures.api._ | ||
import core.utils.Money._ | ||
import core.db._ | ||
import phoenix.failures.ShippingMethodFailures.ShippingMethodNotFoundByName | ||
import phoenix.models.Reasons | ||
import phoenix.models.location.Region | ||
import phoenix.models.shipping.{ShippingMethod, ShippingMethods} | ||
import phoenix.payloads.GiftCardPayloads.GiftCardCreateByCsr | ||
import phoenix.payloads.OrderPayloads.UpdateOrderPayload | ||
import phoenix.payloads.PaymentPayloads.GiftCardPayment | ||
import phoenix.payloads.UpdateShippingMethod | ||
import phoenix.responses.AddressResponse | ||
import phoenix.responses.giftcards.GiftCardResponse | ||
import phoenix.utils.seeds.Factories | ||
|
||
class CouponsIntegrationTest | ||
extends IntegrationTestBase | ||
|
@@ -173,6 +184,64 @@ class CouponsIntegrationTest | |
} | ||
} | ||
|
||
"→ Coupon code from cancelled order can be reused" in new Coupon_AnyQualifier_PercentOff { | ||
import slick.jdbc.PostgresProfile.api._ | ||
|
||
override def couponUsageRules = CouponUsageRules( | ||
isUnlimitedPerCode = false, | ||
usesPerCode = Some(1), | ||
isUnlimitedPerCustomer = false, | ||
usesPerCustomer = Some(1) | ||
) | ||
|
||
// TODO: extract CheckoutFixture and reuse it here (more refactoring will be needed for that) @michalrus | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
val (customer, customerLoginData) = api_newCustomerWithLogin() | ||
val skuCode = ProductSku_ApiFixture().skuCode | ||
val refNum = api_newCustomerCart(customer.id).referenceNumber | ||
|
||
// Place the order. | ||
{ | ||
val addressPayload = randomAddress(regionId = Region.californiaId) | ||
val address = customersApi(customer.id).addresses | ||
.create(addressPayload) | ||
.as[AddressResponse] | ||
|
||
val (shipMethod, reason) = (for { | ||
_ ← * <~ ShippingMethods.createAll(Factories.shippingMethods) | ||
shipMethodName = ShippingMethod.expressShippingNameForAdmin | ||
shipMethod ← * <~ ShippingMethods | ||
.filter(_.adminDisplayName === shipMethodName) | ||
.mustFindOneOr(ShippingMethodNotFoundByName(shipMethodName)) | ||
reason ← * <~ Reasons.create(Factories.reason(defaultAdmin.id)) | ||
} yield (shipMethod, reason)).gimme | ||
|
||
val cartApi = cartsApi(refNum) | ||
cartApi.lineItems.add(Seq(UpdateLineItemsPayload(skuCode, 2))).mustBeOk() | ||
cartApi.shippingAddress.updateFromAddress(address.id).mustBeOk() | ||
cartApi.shippingMethod | ||
.update(UpdateShippingMethod(shipMethod.id)) | ||
.asTheResult[CartResponse] | ||
cartApi.coupon.add(couponCode).asTheResult[CartResponse] | ||
val grandTotal = cartApi.get.asTheResult[CartResponse].totals.total | ||
val gcCode = giftCardsApi | ||
.create(GiftCardCreateByCsr(grandTotal, reason.id)) | ||
.as[GiftCardResponse] | ||
.code | ||
cartApi.payments.giftCard.add(GiftCardPayment(gcCode, grandTotal.some)).mustBeOk() | ||
cartApi.checkout().as[OrderResponse] | ||
} | ||
|
||
// Cancel. | ||
ordersApi(refNum).update(UpdateOrderPayload(Order.Canceled)).as[OrderResponse].orderState must === ( | ||
Order.Canceled) | ||
|
||
// Try to reuse that same coupon. | ||
val refNum2 = api_newCustomerCart(customer.id).referenceNumber | ||
cartsApi(refNum2).lineItems.add(Seq(UpdateLineItemsPayload(skuCode, 2))).mustBeOk() | ||
cartsApi(refNum2).coupon.add(couponCode).asTheResult[CartResponse] | ||
} | ||
|
||
trait CartCouponFixture extends StoreAdmin_Seed with Coupon_TotalQualifier_PercentOff { | ||
|
||
val skuCode = ProductSku_ApiFixture(skuPrice = 3100).skuCode | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import org.json4s.JsonAST.JNull | |
import org.json4s.JsonDSL._ | ||
import org.scalatest.SuiteMixin | ||
import phoenix.models.catalog.Catalog | ||
import phoenix.models.coupon.CouponUsageRules | ||
import phoenix.models.promotion.Promotion | ||
import phoenix.payloads.CouponPayloads.CreateCoupon | ||
import phoenix.payloads.ProductPayloads.CreateProductPayload | ||
|
@@ -170,12 +171,14 @@ trait ApiFixtures extends SuiteMixin with HttpSupport with PhoenixAdminApi with | |
trait CouponFixtureBase { | ||
def couponActiveFrom: Instant = Instant.now.minus(1, DAYS) | ||
def couponActiveTo: Option[Instant] = None | ||
def couponUsageRules: CouponUsageRules = | ||
CouponUsageRules(isUnlimitedPerCode = true, isUnlimitedPerCustomer = true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’d argue they shouldn’t have defaults, either. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also cake pattern here is pretty unintuitive. I have to
instead of
|
||
|
||
def promotion: PromotionResponse | ||
|
||
lazy val coupon = couponsApi | ||
.create( | ||
CreateCoupon(couponAttrs(couponActiveFrom, couponActiveTo), | ||
CreateCoupon(couponAttrs(couponActiveFrom, couponActiveTo, couponUsageRules), | ||
promotion.id, | ||
singleCode = Some(Lorem.letterify("?????")), | ||
generateCodes = None))(implicitly, defaultAdminAuth) | ||
|
@@ -185,19 +188,17 @@ trait ApiFixtures extends SuiteMixin with HttpSupport with PhoenixAdminApi with | |
|
||
lazy val couponCode = coupon.code | ||
|
||
protected def couponAttrs(activeFrom: Instant, activeTo: Option[Instant]): Map[String, Json] = { | ||
val usageRules = { | ||
("isExclusive" → true) ~ | ||
("isUnlimitedPerCode" → true) ~ | ||
("isUnlimitedPerCustomer" → true) | ||
}.asShadowVal(t = "usageRules") | ||
protected def couponAttrs(activeFrom: Instant, | ||
activeTo: Option[Instant], | ||
usageRules: CouponUsageRules): Map[String, Json] = { | ||
import org.json4s.Extraction.decompose | ||
|
||
val commonAttrs = Map[String, Any]( | ||
"name" → "Order coupon", | ||
"storefrontName" → "Order coupon", | ||
"description" → "Order coupon description", | ||
"details" → "Order coupon details".richText, | ||
"usageRules" → usageRules, | ||
"usageRules" → decompose(usageRules).asShadowVal(t = "usageRules"), | ||
"activeFrom" → activeFrom | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it’s
None
… just… don’t call it.