Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ Removes the shipping method from the cart.
+ Attributes (UpdateAddressPayload)
+ Response 200 (application/json)
+ Attributes (FullOrder)


### Create or update [PUT /v1/my/cart/shipping-address]

+ Request (application/json)
+ Attributes(CreateAddressPayload)
+ Response 200 (application/json)
+ Attributes (Address)

### Delete [DELETE]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.time.Instant
import core.db._
import phoenix.models.location.{Address, Addresses, Region, Regions}
import phoenix.models.traits.Addressable
import phoenix.payloads.AddressPayloads.UpdateAddressPayload
import phoenix.payloads.AddressPayloads.{CreateAddressPayload, UpdateAddressPayload}
import shapeless._
import slick.jdbc.PostgresProfile.api._

Expand Down Expand Up @@ -53,6 +53,20 @@ object OrderShippingAddress {
phoneNumber = p.phoneNumber.fold(a.phoneNumber)(Some(_))
)
}

def fromCreatePatchPayload(a: OrderShippingAddress, p: CreateAddressPayload) = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Anna-ZZZ might not like the identifiers. I’m cool with them, but let’s wait before merge, maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um, you're right, will fix them.

OrderShippingAddress(
id = a.id,
cordRef = a.cordRef,
regionId = p.regionId,
name = p.name,
address1 = p.address1,
address2 = p.address2.fold(a.address2)(Some(_)),
city = p.city,
zip = p.zip,
phoneNumber = p.phoneNumber.fold(a.phoneNumber)(Some(_))
)
}
}

class OrderShippingAddresses(tag: Tag)
Expand Down
5 changes: 5 additions & 0 deletions phoenix-scala/phoenix/app/phoenix/routes/Customer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ object Customer {
payload)
}
} ~
(put & pathEnd & entity(as[CreateAddressPayload])) { payload ⇒
mutateOrFailures {
CartShippingAddressUpdater.createOrUpdateShippingAddress(auth.model, payload)
}
} ~
(delete & pathEnd) {
deleteOrFailures {
CartShippingAddressUpdater.removeShippingAddress(auth.model)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ object CartRoutes {
Some(refNum))
}
} ~
(put & pathEnd & entity(as[CreateAddressPayload])) { payload ⇒
mutateOrFailures {
CartShippingAddressUpdater.createOrUpdateShippingAddress(auth.model,
payload,
Some(refNum))
}
} ~
(delete & pathEnd) {
mutateOrFailures {
CartShippingAddressUpdater.removeShippingAddress(auth.model, Some(refNum))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import phoenix.services.{CartValidator, LogActivity}
import slick.jdbc.PostgresProfile.api._
import phoenix.utils.aliases._
import core.db._
import cats.implicits._

object CartShippingAddressUpdater {

Expand All @@ -23,6 +24,13 @@ object CartShippingAddressUpdater {
def mustFindShipAddressForCart(cart: Cart)(implicit ec: EC): DbResultT[OrderShippingAddress] =
OrderShippingAddresses.findByOrderRef(cart.refNum).mustFindOneOr(NoShipAddress(cart.refNum))

private def createShippingAddress(cart: Cart, payload: CreateAddressPayload)(
implicit ec: EC): DbResultT[OrderShippingAddress] =
for {
newAddress ← * <~ Addresses.create(Address.fromPayload(payload, cart.accountId))
shippingAddress ← * <~ OrderShippingAddresses.copyFromAddress(newAddress, cart.refNum)
} yield shippingAddress

def createShippingAddressFromAddressId(originator: User,
addressId: Int,
refNum: Option[String] = None)(
Expand Down Expand Up @@ -83,6 +91,31 @@ object CartShippingAddressUpdater {
buildOneShipping(shipAddress, region))
} yield TheResponse.validated(response, validated)

def createOrUpdateShippingAddress(originator: User,
payload: CreateAddressPayload,
refNum: Option[String] = None)(
implicit ec: EC,
db: DB,
ac: AC,
ctx: OC): DbResultT[TheResponse[CartResponse]] =
for {
cart ← * <~ getCartByOriginator(originator, refNum)
shippingAddress ← * <~ OrderShippingAddresses
.findByOrderRef(cart.refNum)
.one
.findOrCreate(createShippingAddress(cart, payload))

region ← * <~ Regions.mustFindById404(shippingAddress.regionId)

patch = OrderShippingAddress.fromCreatePatchPayload(shippingAddress, payload)
_ ← * <~ OrderShippingAddresses.update(shippingAddress, patch)
validated ← * <~ CartValidator(cart).validate()
response ← * <~ CartResponse.buildRefreshed(cart)
_ ← * <~ LogActivity().orderShippingAddressUpdated(originator,
response,
buildOneShipping(shippingAddress, region))
} yield TheResponse.validated(response, validated)

def removeShippingAddress(originator: User, refNum: Option[String] = None)(
implicit ec: EC,
db: DB,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import phoenix.models.account._
import phoenix.models.cord.OrderShippingAddresses
import phoenix.models.location.{Address, Addresses, Country, Region}
import phoenix.payloads.AddressPayloads.CreateAddressPayload
import phoenix.responses.AddressResponse
import phoenix.payloads.CartPayloads.CreateCart
import phoenix.responses.{AddressResponse, CustomerResponse, TheResponse}
import phoenix.responses.PublicResponses.CountryWithRegions
import phoenix.responses.cord.CartResponse
import testutils._
import testutils.apis.{PhoenixAdminApi, PhoenixPublicApi}
import testutils.apis.{PhoenixAdminApi, PhoenixPublicApi, PhoenixStorefrontApi}
import testutils.fixtures.BakedFixtures
import testutils.fixtures.api.{ApiFixtureHelpers, randomAddress}

Expand All @@ -18,6 +20,7 @@ class AddressesIntegrationTest
with DefaultJwtAdminAuth
with ApiFixtureHelpers
with PhoenixAdminApi
with PhoenixStorefrontApi
with PhoenixPublicApi
with BakedFixtures {

Expand All @@ -31,15 +34,10 @@ class AddressesIntegrationTest
}

"POST /v1/customers/:customerId/addresses" - {
"creates an address" in new Customer_Seed {
val payload = CreateAddressPayload(name = "Home Office",
regionId = 1,
address1 = "3000 Coolio Dr",
city = "Seattle",
zip = "55555")
"creates an address" in new Customer_Seed with AddressFixture {
val newAddress =
customersApi(customer.accountId).addresses.create(payload).as[AddressResponse]
newAddress.name must === (payload.name)
customersApi(customer.accountId).addresses.create(addressPayload).as[AddressResponse]
newAddress.name must === (addressPayload.name)
newAddress.isDefault must === (Some(false))
}
}
Expand Down Expand Up @@ -74,31 +72,23 @@ class AddressesIntegrationTest
}

"PATCH /v1/customers/:customerId/addresses/:addressId" - {
"can be edited" in new CustomerAddress_Baked {
val payload = CreateAddressPayload(name = "Home Office",
regionId = 1,
address1 = "3000 Coolio Dr",
city = "Seattle",
zip = "55555")
(payload.name, payload.address1) must !==((address.name, address.address1))
"can be edited" in new CustomerAddress_Baked with AddressFixture {
(addressPayload.name, addressPayload.address1) must !==((address.name, address.address1))

val updated =
customersApi(customer.accountId).address(address.id).edit(payload).as[AddressResponse]
val updated = customersApi(customer.accountId)
.address(address.id)
.edit(addressPayload)
.as[AddressResponse]

(updated.name, updated.address1) must === ((payload.name, payload.address1))
(updated.name, updated.address1) must === ((addressPayload.name, addressPayload.address1))
}
}

"DELETE /v1/customers/:customerId/addresses/:addressId" - {
"can be deleted" in new CustomerAddress_Baked {
"can be deleted" in new CustomerAddress_Baked with AddressFixture {

//notice the payload is a default shipping address. Delete should make it not default.
val payload = CreateAddressPayload(name = "Delete Me",
regionId = 1,
address1 = "5000 Delete Dr",
city = "Deattle",
zip = "666",
isDefault = true)
val payload = addressPayload.copy(isDefault = true)

val newAddress: AddressResponse =
customersApi(customer.accountId).addresses.create(payload).as[AddressResponse]
Expand Down Expand Up @@ -144,6 +134,42 @@ class AddressesIntegrationTest
}
}

"Create /v1/my/addresses" - {
"POST shipping addresses into a cart adds it to customer details as well" in new AddressFixture {
withNewCustomerAuth(TestLoginData.random) { implicit auth ⇒
val cart = cartsApi.create(CreateCart(customerId = auth.customerId.some)).as[CartResponse]

storefrontCartsApi.shippingAddress.create(addressPayload).as[TheResponse[CartResponse]]

customersApi(auth.customerId).addresses.get
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Anna-ZZZ, I think that was my question befooore, hmm. Maybe in withNewCustomerAuth block one shouldn’t have access to admin API like customersApi, and it should be explicitly wrapped in withDefaultAdminAuth or similar. Which would mean that we’d need to remove with DefaultAdminAuth trait and wrap alsmost all existing tests. Hmmm. Well. ¯\_(ツ)_/¯

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request is sent with customer auth anyways because of implicits

.as[Seq[AddressResponse]]
.onlyElement
.address1 must === (addressPayload.address1)

cartsApi(cart.referenceNumber)
.get()
.asTheResult[CartResponse]
.shippingAddress
.value
.address1 must === (addressPayload.address1)
}
}

"PUT shipping addresses must be idempotent" in new AddressFixture {
withNewCustomerAuth(TestLoginData.random) { implicit auth ⇒
cartsApi.create(CreateCart(customerId = auth.customerId.some)).as[CartResponse]

storefrontCartsApi.shippingAddress.createOrUpdate(addressPayload).mustBeOk()
storefrontCartsApi.shippingAddress.createOrUpdate(addressPayload).mustBeOk()

customersApi(auth.customerId).addresses.get
.as[Seq[AddressResponse]]
.onlyElement
.address1 must === (addressPayload.address1)
}
}
}

"GET /v1/my/addresses" - {
"retrieves a customer's addresses" in {
val (customer, loginData) = api_newCustomerWithLogin()
Expand Down Expand Up @@ -211,4 +237,13 @@ class AddressesIntegrationTest
trait NoDefaultAddressFixture extends CustomerAddress_Baked with EmptyCustomerCart_Baked {
val shippingAddress = OrderShippingAddresses.copyFromAddress(address, cart.refNum).gimme
}

trait AddressFixture {
val addressPayload = CreateAddressPayload(name = "Home Office",
regionId = 1,
address1 = "3000 Coolio Dr",
city = "Seattle",
zip = "55555")
}

}
14 changes: 14 additions & 0 deletions phoenix-scala/phoenix/test/integration/testutils/HttpSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ trait HttpSupport
dispatchRequest(request, jwtCookie)
}

def PUT(path: String, rawBody: String, jwtCookie: Option[Cookie]): HttpResponse = {
val request = HttpRequest(method = HttpMethods.PUT,
uri = pathToAbsoluteUrl(path),
entity = HttpEntity.Strict(
ContentTypes.`application/json`,
ByteString(rawBody)
))

dispatchRequest(request, jwtCookie)
}

def POST(path: String, jwtCookie: Option[Cookie]): HttpResponse =
dispatchRequest(HttpRequest(method = HttpMethods.POST, uri = pathToAbsoluteUrl(path)),
jwtCookie)
Expand All @@ -143,6 +154,9 @@ trait HttpSupport
def POST[T <: AnyRef](path: String, payload: T, jwtCookie: Option[Cookie]): HttpResponse =
POST(path, writeJson(payload), jwtCookie)

def PUT[T <: AnyRef](path: String, payload: T, jwtCookie: Option[Cookie]): HttpResponse =
PUT(path, writeJson(payload), jwtCookie)

def PATCH[T <: AnyRef](path: String, payload: T, jwtCookie: Option[Cookie]): HttpResponse =
PATCH(path, writeJson(payload), jwtCookie)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package testutils.apis

import akka.http.scaladsl.model.HttpResponse
import cats.implicits._
import phoenix.payloads.AddressPayloads.CreateAddressPayload
import phoenix.payloads.CartPayloads.CheckoutCart
import phoenix.payloads.LineItemPayloads.UpdateLineItemsPayload
import phoenix.payloads.PaymentPayloads.{CreateApplePayPayment, CreateCreditCardFromTokenPayload}
Expand All @@ -11,6 +12,13 @@ trait PhoenixStorefrontApi extends HttpSupport { self: FoxSuite ⇒

val rootPrefix: String = "v1/my"

object accountApi {
val accountPath = s"$rootPrefix/account"

def getAccount()(implicit ca: TestCustomerAuth): HttpResponse =
GET(accountPath, ca.jwtCookie.some)
}

case class storefrontProductsApi(reference: String) {
val productPath = s"$rootPrefix/products/$reference/baked"

Expand Down Expand Up @@ -43,7 +51,17 @@ trait PhoenixStorefrontApi extends HttpSupport { self: FoxSuite ⇒

def searchByRegion(countryCode: String)(implicit aa: TestCustomerAuth): HttpResponse =
GET(s"$shippingMethods/$countryCode", aa.jwtCookie.some)
}

object shippingAddress {
val shippingAddress = s"$cartPath/shipping-address"

def create(payload: CreateAddressPayload)(implicit ca: TestCustomerAuth): HttpResponse =
POST(shippingAddress, payload, ca.jwtCookie.some)

def createOrUpdate(payload: CreateAddressPayload)(
implicit ca: TestCustomerAuth): HttpResponse =
PUT(shippingAddress, payload, ca.jwtCookie.some)
}
}

Expand Down