Setup future usage
This commit is contained in:
@@ -7,12 +7,13 @@ Joi.address = () => {
|
||||
first_name: Joi.string().required(),
|
||||
last_name: Joi.string().required(),
|
||||
address_1: Joi.string().required(),
|
||||
address_2: Joi.string(),
|
||||
address_2: Joi.string().allow(""),
|
||||
city: Joi.string().required(),
|
||||
country_code: Joi.string().required(),
|
||||
province: Joi.string(),
|
||||
province: Joi.string().allow(""),
|
||||
postal_code: Joi.string().required(),
|
||||
metadata: Joi.object()
|
||||
phone: Joi.string().required(),
|
||||
metadata: Joi.object(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,14 @@ class BasePaymentService extends BaseService {
|
||||
deletePayment() {
|
||||
throw Error("deletePayment must be overridden by the child class")
|
||||
}
|
||||
|
||||
/**
|
||||
* If the payment provider can save a payment method this function will
|
||||
* retrieve them.
|
||||
*/
|
||||
retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
}
|
||||
|
||||
export default BasePaymentService
|
||||
|
||||
@@ -50,7 +50,22 @@ class StripeProviderService extends PaymentService {
|
||||
return status
|
||||
}
|
||||
|
||||
async retrieveSavedMethods(customer) {
|
||||
if (customer.metadata && customer.metadata.stripe_id) {
|
||||
const methods = await this.stripe_.paymentMethods.list({
|
||||
customer: customer.metadata.stripe_id, type: "card"
|
||||
})
|
||||
|
||||
return methods.data
|
||||
}
|
||||
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
async retrieveCustomer(customerId) {
|
||||
if (!customerId) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return this.stripe_.customers.retrieve(customerId)
|
||||
}
|
||||
|
||||
@@ -104,6 +119,7 @@ class StripeProviderService extends PaymentService {
|
||||
customer: stripeCustomerId,
|
||||
amount: amount * 100, // Stripe amount is in cents
|
||||
currency: currency_code,
|
||||
setup_future_usage: "on-session",
|
||||
capture_method: "manual",
|
||||
metadata: { cart_id: `${cart._id}` },
|
||||
})
|
||||
|
||||
@@ -29,8 +29,14 @@ class CartSubscriber {
|
||||
|
||||
const customer = await this.customerService_.retrieve(customer_id)
|
||||
|
||||
const stripeSession = payment_sessions.find(s => s.provider_id === "stripe")
|
||||
|
||||
if (!stripeSession) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const paymentIntent = await this.stripeProviderService_.retrievePayment(
|
||||
cart
|
||||
stripeSession.data
|
||||
)
|
||||
|
||||
let stripeCustomer = await this.stripeProviderService_.retrieveCustomer(
|
||||
|
||||
18
packages/medusa/src/api/middlewares/authenticate-customer.js
Normal file
18
packages/medusa/src/api/middlewares/authenticate-customer.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import passport from "passport"
|
||||
|
||||
export default () => {
|
||||
// Always go to next
|
||||
return (req, res, next) => {
|
||||
passport.authenticate(
|
||||
["jwt", "bearer"],
|
||||
{ session: false },
|
||||
(err, user, info) => {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
req.user = user
|
||||
return next()
|
||||
}
|
||||
)(req, res, next)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { default as authenticateCustomer } from "./authenticate-customer"
|
||||
import { default as authenticate } from "./authenticate"
|
||||
import { default as wrap } from "./await-middleware"
|
||||
|
||||
export default {
|
||||
authenticate,
|
||||
wrap
|
||||
authenticateCustomer,
|
||||
wrap,
|
||||
}
|
||||
|
||||
@@ -28,7 +28,17 @@ export default async (req, res) => {
|
||||
regionId = regions[0]._id
|
||||
}
|
||||
|
||||
let cart = await cartService.create({ region_id: regionId })
|
||||
let customerId = ""
|
||||
if (req.user && req.user.customer_id) {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.retrieve(req.user.customer_id)
|
||||
customerId = customer._id
|
||||
}
|
||||
|
||||
let cart = await cartService.create({
|
||||
region_id: regionId,
|
||||
customer_id: customerId,
|
||||
})
|
||||
|
||||
if (value.items) {
|
||||
await Promise.all(
|
||||
|
||||
@@ -3,6 +3,18 @@ export default async (req, res) => {
|
||||
try {
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
let cart = await cartService.retrieve(id)
|
||||
|
||||
// If there is a logged in user add the user to the cart
|
||||
if (req.user && req.user.customer_id) {
|
||||
if (!cart.customer_id || cart.customer_id !== req.user.customer_id) {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.retrieve(req.user.customer_id)
|
||||
|
||||
cart = await cartService.updateCustomerId(id, customer._id)
|
||||
cart = await cartService.updateEmail(id, customer.email)
|
||||
}
|
||||
}
|
||||
|
||||
cart = await cartService.decorate(cart, [], ["region"])
|
||||
res.json({ cart })
|
||||
} catch (err) {
|
||||
|
||||
@@ -12,7 +12,6 @@ export default async (req, res) => {
|
||||
discounts: Validator.array().items({
|
||||
code: Validator.string(),
|
||||
}),
|
||||
customer_id: Validator.string(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
@@ -28,10 +27,6 @@ export default async (req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
if (value.customer_id) {
|
||||
await cartService.updateCustomerId(id, value.customer_id)
|
||||
}
|
||||
|
||||
if (value.region_id) {
|
||||
await cartService.setRegion(id, value.region_id)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id, address_id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
address: Validator.address(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
try {
|
||||
const customer = await customerService.addAddress(id, value.address)
|
||||
const data = await customerService.decorate(
|
||||
customer,
|
||||
["email", "first_name", "last_name", "shipping_addresses"],
|
||||
["orders"]
|
||||
)
|
||||
res.json({ customer: data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export default async (req, res) => {
|
||||
const { id, address_id } = req.params
|
||||
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
try {
|
||||
const customer = await customerService.removeAddress(id, address_id)
|
||||
const data = await customerService.decorate(
|
||||
customer,
|
||||
["email", "first_name", "last_name", "shipping_addresses"],
|
||||
["orders"]
|
||||
)
|
||||
res.json({ customer: data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export default async (req, res) => {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
let customer = await customerService.retrieve(id)
|
||||
customer = customerService.decorate(
|
||||
customer._id,
|
||||
customer,
|
||||
["email", "first_name", "last_name", "shipping_addresses"],
|
||||
["orders"]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
try {
|
||||
const storeService = req.scope.resolve("storeService")
|
||||
const paymentProviderService = req.scope.resolve("paymentProviderService")
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
|
||||
let customer = await customerService.retrieve(id)
|
||||
|
||||
const store = await storeService.retrieve()
|
||||
|
||||
const methods = await Promise.all(
|
||||
store.payment_providers.map(async next => {
|
||||
const provider = paymentProviderService.retrieveProvider(next)
|
||||
|
||||
const pMethods = await provider.retrieveSavedMethods(customer)
|
||||
return pMethods.map(m => ({
|
||||
provider_id: next,
|
||||
data: m,
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
res.json({ payment_methods: methods.flat() })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -29,5 +29,26 @@ export default app => {
|
||||
"/:id/password",
|
||||
middlewares.wrap(require("./update-password").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/addresses",
|
||||
middlewares.wrap(require("./create-address").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/addresses/:address_id",
|
||||
middlewares.wrap(require("./update-address").default)
|
||||
)
|
||||
|
||||
route.delete(
|
||||
"/:id/addresses/:address_id",
|
||||
middlewares.wrap(require("./delete-address").default)
|
||||
)
|
||||
|
||||
route.get(
|
||||
"/:id/payment-methods",
|
||||
middlewares.wrap(require("./get-payment-methods").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id, address_id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
address: Validator.address(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
try {
|
||||
const customer = await customerService.updateAddress(
|
||||
id,
|
||||
address_id,
|
||||
value.address
|
||||
)
|
||||
const data = await customerService.decorate(
|
||||
customer,
|
||||
["email", "first_name", "last_name", "shipping_addresses"],
|
||||
["orders"]
|
||||
)
|
||||
res.json({ customer: data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Router } from "express"
|
||||
import cors from "cors"
|
||||
|
||||
import middlewares from "../../middlewares"
|
||||
|
||||
import authRoutes from "./auth"
|
||||
import productRoutes from "./products"
|
||||
import cartRoutes from "./carts"
|
||||
@@ -22,6 +24,8 @@ export default (app, container, config) => {
|
||||
})
|
||||
)
|
||||
|
||||
route.use(middlewares.authenticateCustomer())
|
||||
|
||||
authRoutes(route)
|
||||
customerRoutes(route)
|
||||
productRoutes(route)
|
||||
|
||||
@@ -12,5 +12,6 @@ export default new mongoose.Schema({
|
||||
country_code: { type: String, required: true },
|
||||
province: { type: String },
|
||||
postal_code: { type: String, required: true },
|
||||
phone: { type: String },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import { BaseService } from "medusa-interfaces"
|
||||
*/
|
||||
class CartService extends BaseService {
|
||||
static Events = {
|
||||
CUSTOMER_UPDATED: "cart.customer_updated",
|
||||
CREATED: "cart.created",
|
||||
UPDATED: "cart.updated",
|
||||
}
|
||||
@@ -207,9 +208,20 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
const region = await this.regionService_.retrieve(region_id)
|
||||
if (region.countries.length === 1) {
|
||||
// Preselect the country if the region only has 1
|
||||
data.shipping_address = {
|
||||
country_code: countries[0],
|
||||
}
|
||||
|
||||
data.billing_address = {
|
||||
country_code: countries[0],
|
||||
}
|
||||
}
|
||||
|
||||
return this.cartModel_
|
||||
.create({
|
||||
...data,
|
||||
region_id: region._id,
|
||||
})
|
||||
.then(result => {
|
||||
@@ -445,10 +457,8 @@ class CartService extends BaseService {
|
||||
*/
|
||||
async updateCustomerId(cartId, customerId) {
|
||||
const cart = await this.retrieve(cartId)
|
||||
const schema = Validator.string()
|
||||
.objectId()
|
||||
.required()
|
||||
const { value, error } = schema.validate(customerId)
|
||||
const schema = Validator.objectId().required()
|
||||
const { value, error } = schema.validate(customerId.toString())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -467,7 +477,7 @@ class CartService extends BaseService {
|
||||
)
|
||||
.then(result => {
|
||||
// Notify subscribers
|
||||
this.eventBus_.emit(CartService.Events.UPDATED, result)
|
||||
this.eventBus_.emit(CartService.Events.CUSTOMER_UPDATED, result)
|
||||
return result
|
||||
})
|
||||
}
|
||||
@@ -499,6 +509,8 @@ class CartService extends BaseService {
|
||||
customer = await this.customerService_.create({ email })
|
||||
}
|
||||
|
||||
const customerChanged = !customer._id.equals(cart.customer_id)
|
||||
|
||||
return this.cartModel_
|
||||
.updateOne(
|
||||
{
|
||||
@@ -513,6 +525,9 @@ class CartService extends BaseService {
|
||||
)
|
||||
.then(result => {
|
||||
// Notify subscribers
|
||||
if (customerChanged) {
|
||||
this.eventBus_.emit(CartService.Events.CUSTOMER_UPDATED, result)
|
||||
}
|
||||
this.eventBus_.emit(CartService.Events.UPDATED, result)
|
||||
return result
|
||||
})
|
||||
@@ -966,17 +981,11 @@ class CartService extends BaseService {
|
||||
// If the country code of a shipping address is set we need to clear it
|
||||
let shippingAddress = cart.shipping_address
|
||||
if (!_.isEmpty(shippingAddress) && shippingAddress.country_code) {
|
||||
shippingAddress.country_code = ""
|
||||
shippingAddress.country_code =
|
||||
region.countries.length === 1 ? region.countries[0] : ""
|
||||
update.shipping_address = shippingAddress
|
||||
}
|
||||
|
||||
// If the country code of a billing address is set we need to clear it
|
||||
let billingAddress = cart.billing_address
|
||||
if (!_.isEmpty(billingAddress) && billingAddress.country_code) {
|
||||
billingAddress.country_code = ""
|
||||
update.billing_address = billingAddress
|
||||
}
|
||||
|
||||
// Shipping methods are determined by region so the user needs to find a
|
||||
// new shipping method
|
||||
if (cart.shipping_methods && cart.shipping_methods.length) {
|
||||
@@ -1000,7 +1009,8 @@ class CartService extends BaseService {
|
||||
|
||||
// Payment methods are region specific so the user needs to find a
|
||||
// new payment method
|
||||
if (!_.isEmpty(cart.payment_method)) {
|
||||
if (!_.isEmpty(cart.payment_method) || cart.payment_sessions.length) {
|
||||
update.payment_sessions = []
|
||||
update.payment_method = undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -255,6 +255,52 @@ class CustomerService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
async addAddress(customerId, address) {
|
||||
const customer = await this.retrieve(customerId)
|
||||
this.validateBillingAddress_(address)
|
||||
|
||||
let shouldAdd = !!customer.shipping_addresses.find(
|
||||
a =>
|
||||
a.country_code === address.country_code &&
|
||||
a.address_1 === address.address_1 &&
|
||||
a.address_2 === address.address_2 &&
|
||||
a.city === address.city &&
|
||||
a.phone === address.phone &&
|
||||
a.postal_code === address.postal_code &&
|
||||
a.province === address.province &&
|
||||
a.first_name === address.first_name &&
|
||||
a.last_name === address.last_name
|
||||
)
|
||||
|
||||
if (shouldAdd) {
|
||||
return this.customerModel_.updateOne(
|
||||
{ _id: customer._id },
|
||||
{ $addToSet: { shipping_addresses: address } }
|
||||
)
|
||||
} else {
|
||||
return customer
|
||||
}
|
||||
}
|
||||
|
||||
async updateAddress(customerId, addressId, address) {
|
||||
const customer = await this.retrieve(customerId)
|
||||
this.validateBillingAddress_(address)
|
||||
|
||||
return this.customerModel_.updateOne(
|
||||
{ _id: customer._id, "shipping_addresses._id": addressId },
|
||||
{ $set: { "shipping_addresses.$": address } }
|
||||
)
|
||||
}
|
||||
|
||||
async removeAddress(customerId, addressId) {
|
||||
const customer = await this.retrieve(customerId)
|
||||
|
||||
return this.customerModel_.updateOne(
|
||||
{ _id: customer._id },
|
||||
{ $pull: { shipping_addresses: { _id: addressId } } }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a customer from a given customer id.
|
||||
* @param {string} customerId - the id of the customer to delete. Must be
|
||||
|
||||
@@ -21,6 +21,10 @@ class OrderSubscriber {
|
||||
|
||||
this.eventBus_.subscribe("order.placed", async order => {
|
||||
await this.customerService_.addOrder(order.customer_id, order._id)
|
||||
await this.customerService_.addAddress(
|
||||
order.customer_id,
|
||||
order.shipping_address
|
||||
)
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("order.placed", async order => {
|
||||
|
||||
Reference in New Issue
Block a user