From 4b4275a8eef2b767ecfef72a9678a4e56b5fdb87 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Fri, 10 Jul 2020 12:25:54 +0200 Subject: [PATCH 1/2] Klarna support --- packages/medusa-payment-klarna/.gitignore | 8 +- .../src/services/klarna-provider.js | 81 +++++++++++-------- packages/medusa/src/services/cart.js | 15 ++++ 3 files changed, 69 insertions(+), 35 deletions(-) diff --git a/packages/medusa-payment-klarna/.gitignore b/packages/medusa-payment-klarna/.gitignore index 134ab70b93..89114faaff 100644 --- a/packages/medusa-payment-klarna/.gitignore +++ b/packages/medusa-payment-klarna/.gitignore @@ -6,5 +6,11 @@ node_modules !index.js !jest.config.js -dist +/dist +/api +/services +/models +/subscribers +/__mocks__ + diff --git a/packages/medusa-payment-klarna/src/services/klarna-provider.js b/packages/medusa-payment-klarna/src/services/klarna-provider.js index 24ac70eb99..b4b73b94cd 100644 --- a/packages/medusa-payment-klarna/src/services/klarna-provider.js +++ b/packages/medusa-payment-klarna/src/services/klarna-provider.js @@ -5,7 +5,7 @@ import { PaymentService } from "medusa-interfaces" class KlarnaProviderService extends PaymentService { static identifier = "klarna" - constructor({ totalsService, regionService }, options) { + constructor({ shippingProfileService, totalsService, regionService }, options) { super() this.options_ = options @@ -22,11 +22,13 @@ class KlarnaProviderService extends PaymentService { this.klarnaOrderManagementUrl_ = "/ordermanagement/v1/orders" - this.backendUrl_ = process.env.BACKEND_URL || "" + this.backendUrl_ = process.env.BACKEND_URL || "https://58721b1f44d9.ngrok.io" this.totalsService_ = totalsService this.regionService_ = regionService + + this.shippingProfileService_ = shippingProfileService } async lineItemsToOrderLines_(cart, taxRate) { @@ -35,12 +37,16 @@ class KlarnaProviderService extends PaymentService { const discount = cart.discounts.find( ({ discount_rule }) => discount_rule.type !== "free_shipping" ) - // If the discount has an item specific allocation method, - // we need to fetch the discount for each item - const itemDiscounts = await this.totalsService_.getAllocationItemDiscounts( - discount, - cart - ) + + let itemDiscounts = [] + if (discount) { + // If the discount has an item specific allocation method, + // we need to fetch the discount for each item + itemDiscounts = await this.totalsService_.getAllocationItemDiscounts( + discount, + cart + ) + } cart.items.forEach((item) => { // For bundles, we create an order line for each item in the bundle @@ -50,20 +56,17 @@ class KlarnaProviderService extends PaymentService { const total_tax_amount = total_amount * taxRate order_lines.push({ + name: item.title, unit_price: c.unit_price, // Medusa does not allow discount on bundles total_discount_amount: 0, quantity: c.quantity, - tax_rate: taxRate, + tax_rate: taxRate * 10000, total_amount, total_tax_amount, }) }) } else { - const total_amount = - item.content.unit_price * item.content.quantity * (taxRate + 1) - const total_tax_amount = total_amount * taxRate - // Find the discount for current item and default to 0 const itemDiscount = (itemDiscounts && @@ -71,19 +74,25 @@ class KlarnaProviderService extends PaymentService { 0 // Withdraw discount from the total item amount - const total_discount_amount = - total_amount - itemDiscount * (taxRate + 1) + const quantity = item.content.quantity + const unit_price = item.content.unit_price * 100 * (taxRate + 1) + const total_discount_amount = itemDiscount * (taxRate + 1) * 100 + const total_amount = unit_price * quantity - total_discount_amount + const total_tax_amount = total_amount * (taxRate / (1 + taxRate)) order_lines.push({ - unit_price: item.content.unit_price, - quantity: item.content.quantity, + name: item.title, + tax_rate: taxRate * 10000, + quantity, + unit_price, total_discount_amount, - tax_rate: taxRate, total_amount, total_tax_amount, }) } }) + + return order_lines } async cartToKlarnaOrder(cart) { @@ -98,9 +107,9 @@ class KlarnaProviderService extends PaymentService { cart.region_id ) - order.order_lines = this.lineItemsToOrderLines_(cart, tax_rate) + order.order_lines = await this.lineItemsToOrderLines_(cart, tax_rate) - if (cart.billing_address) { + if (!_.isEmpty(cart.billing_address)) { order.billing_address = { email: cart.email, street_address: cart.billing_address.address_1, @@ -112,15 +121,15 @@ class KlarnaProviderService extends PaymentService { } // TODO: Check if country matches ISO - if (cart.billing_address.country) { + if (!_.isEmpty(cart.billing_address) && cart.billing_address.country) { order.purchase_country = cart.billing_address.country } else { // Defaults to Sweden order.purchase_country = "SE" } - order.order_amount = this.totalsService_.getTotal(cart) - order.order_tax_amount = this.totalsService_.getTaxTotal(cart) + order.order_amount = await this.totalsService_.getTotal(cart) * 100 + order.order_tax_amount = await this.totalsService_.getTaxTotal(cart) * 100 // TODO: Check if currency matches ISO order.purchase_currency = currency_code @@ -133,10 +142,13 @@ class KlarnaProviderService extends PaymentService { address_update: `${this.backendUrl_}/klarna/hooks/address`, } + + const shippingOptions = await this.shippingProfileService_.fetchCartOptions(cart) + // If the cart does not have shipping methods yet, preselect one from // shipping_options and set the selected shipping method - if (!cart.shipping_methods) { - const shipping_method = cart.shipping_options[0] + if (!cart.shipping_methods.length) { + const shipping_method = shippingOptions[0] order.selected_shipping_option = { id: shipping_method._id, // TODO: Add shipping method name @@ -146,10 +158,7 @@ class KlarnaProviderService extends PaymentService { // Medusa tax rate of e.g. 0.25 (25%) needs to be 2500 in Klarna tax_rate: tax_rate * 10000, } - } - - // If the cart does have shipping methods, set the selected shipping method - if (cart.shipping_methods) { + } else { const shipping_method = cart.shipping_methods[0] order.selected_shipping_option = { id: shipping_method._id, @@ -160,7 +169,9 @@ class KlarnaProviderService extends PaymentService { } } - order.shipping_options = cart.shipping_options.map((so) => ({ + // If the cart does have shipping methods, set the selected shipping method + + order.shipping_options = shippingOptions.map((so) => ({ id: so._id, name: so.provider_id, price: so.price * (1 + tax_rate) * 100, @@ -201,7 +212,8 @@ class KlarnaProviderService extends PaymentService { async createPayment(cart) { try { const order = await this.cartToKlarnaOrder(cart) - return this.klarna_.post(this.klarnaOrderUrl__, order) + return this.klarna_.post(this.klarnaOrderUrl_, order) + .then(({ data }) => data) } catch (error) { throw error } @@ -280,10 +292,11 @@ class KlarnaProviderService extends PaymentService { * @param {Object} data - the update object * @returns {Object} updated order */ - async updatePayment(order, update) { + async updatePayment(paymentData, cart) { try { - const { data } = order.payment_method - return this.klarna_.post(`${this.klarnaOrderUrl_}/${data.id}`, update) + const order = await this.cartToKlarnaOrder(cart) + return this.klarna_.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order) + .then(({ data }) => data) } catch (error) { throw error } diff --git a/packages/medusa/src/services/cart.js b/packages/medusa/src/services/cart.js index 5ecb368a34..d33c0ec021 100644 --- a/packages/medusa/src/services/cart.js +++ b/packages/medusa/src/services/cart.js @@ -968,6 +968,21 @@ class CartService extends BaseService { update.shipping_methods = [] } + //if (cart.items.length && cart.payment_sessions.length) { + // update.payment_sessions = await Promise.all( + // region.payment_providers.map(async pId => { + // const data = await this.paymentProviderService_.createSession(pId, { + // ...cart, + // ...update, + // }) + // return { + // provider_id: pId, + // data, + // } + // }) + // ) + //} + // Payment methods are region specific so the user needs to find a // new payment method if (!_.isEmpty(cart.payment_method)) { From d930d007232cadc304a101f45bdc67f8a342c31b Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Fri, 10 Jul 2020 15:41:42 +0200 Subject: [PATCH 2/2] Klarna orders --- .../src/api/routes/hooks/address.js | 27 +++++++++---------- .../src/api/routes/hooks/index.js | 4 +-- .../src/api/routes/hooks/push.js | 4 +-- .../src/api/routes/hooks/shipping.js | 19 ++++++------- .../src/services/klarna-provider.js | 26 +++++++++++++----- .../src/models/schemas/shipping-method.js | 1 + 6 files changed, 45 insertions(+), 36 deletions(-) diff --git a/packages/medusa-payment-klarna/src/api/routes/hooks/address.js b/packages/medusa-payment-klarna/src/api/routes/hooks/address.js index 9667f02b16..fdf1c2c87b 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/address.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/address.js @@ -3,30 +3,29 @@ export default async (req, res) => { const { shipping_address, merchant_data } = req.body try { - const cartService = req.resolve("cartService") - const klarnaProviderService = req.resolve("pp_klarna") + const cartService = req.scope.resolve("cartService") + const klarnaProviderService = req.scope.resolve("pp_klarna") const cart = await cartService.retrieve(merchant_data) if (shipping_address) { const updatedAddress = { - email: shipping_address.email, - street_address: shipping_address.address_1, - street_address2: shipping_address.address_2, - postal_code: shipping_address.postal_code, + first_name: shipping_address.given_name, + last_name: shipping_address.family_name, + address_1: shipping_address.street_address, + address_2: shipping_address.street_address2, city: shipping_address.city, - country: shipping_address.country_code, + country_code: shipping_address.country, + postal_code: shipping_address.postal_code, } - - await cartService.update(cart._id, { - email: shipping_address.email, - shipping_address: updatedAddress, - billing_address: updatedAddress, - }) + + await cartService.updateShippingAddress(cart._id, updatedAddress) + await cartService.updateBillingAddress(cart._id, updatedAddress) + await cartService.updateEmail(cart._id, shipping_address.email) // Fetch and return updated Klarna order const updatedCart = await cartService.retrieve(cart._id) - const order = klarnaProviderService.cartToKlarnaOrder(updatedCart) + const order = await klarnaProviderService.cartToKlarnaOrder(updatedCart) res.json(order) return } else { diff --git a/packages/medusa-payment-klarna/src/api/routes/hooks/index.js b/packages/medusa-payment-klarna/src/api/routes/hooks/index.js index d3db4fc345..16edcd40fb 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/index.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/index.js @@ -4,10 +4,10 @@ import middlewares from "../../middlewares" const route = Router() export default (app) => { - app.use("/hooks", route) + app.use("/klarna", route) - route.post("/shipping", middlewares.wrap(require("./shippping").default)) route.post("/address", middlewares.wrap(require("./address").default)) + route.post("/shipping", middlewares.wrap(require("./shipping").default)) route.post("/push", middlewares.wrap(require("./push").default)) return app diff --git a/packages/medusa-payment-klarna/src/api/routes/hooks/push.js b/packages/medusa-payment-klarna/src/api/routes/hooks/push.js index a28d4ec568..7c4bb8ca98 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/push.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/push.js @@ -2,8 +2,8 @@ export default async (req, res) => { const { klarna_order_id } = req.query try { - const orderService = req.resolve("orderService") - const klarnaProviderService = req.resolve("pp_klarna") + const orderService = req.scope.resolve("orderService") + const klarnaProviderService = req.scope.resolve("pp_klarna") const klarnaOrder = await klarnaProviderService.retrieveCompletedOrder( klarna_order_id diff --git a/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js b/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js index afac166c8f..4e41d230b6 100644 --- a/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js +++ b/packages/medusa-payment-klarna/src/api/routes/hooks/shipping.js @@ -3,27 +3,24 @@ export default async (req, res) => { const { merchant_data, selected_shipping_option } = req.body try { - const cartService = req.resolve("cartService") - const klarnaProviderService = req.resolve("pp_klarna") + const cartService = req.scope.resolve("cartService") + const klarnaProviderService = req.scope.resolve("pp_klarna") + const shippingProfileService = req.scope.resolve("shippingProfileService") const cart = await cartService.retrieve(merchant_data) - const updatedMethod = cart.shipping_options.find( - (so) => so._id === selected_shipping_option.id - ) + const shippingOptions = await shippingProfileService.fetchCartOptions(cart) - if (updatedMethod) { - await cartService.update(cart._id, { - shipping_methods: [updatedMethod], - }) + const option = shippingOptions.find(({ _id }) => _id === selected_shipping_option.id) + + if (option) { + await cartService.addShippingMethod(cart._id, option._id, option.data) // Fetch and return updated Klarna order const updatedCart = await cartService.retrieve(cart._id) const order = klarnaProviderService.cartToKlarnaOrder(updatedCart) res.json(order) - return } else { res.sendStatus(400) - return } } catch (error) { throw error diff --git a/packages/medusa-payment-klarna/src/services/klarna-provider.js b/packages/medusa-payment-klarna/src/services/klarna-provider.js index b4b73b94cd..3dadbc7a10 100644 --- a/packages/medusa-payment-klarna/src/services/klarna-provider.js +++ b/packages/medusa-payment-klarna/src/services/klarna-provider.js @@ -92,6 +92,19 @@ class KlarnaProviderService extends PaymentService { } }) + if (cart.shipping_methods.length) { + const shippingMethod = cart.shipping_methods[0] + const price = shippingMethod.price + order_lines.push({ + name: `${shippingMethod.name}`, + quantity: 1, + unit_price: price * (1 + taxRate) * 100, + tax_rate: taxRate * 10000, + total_amount: price * (1 + taxRate) * 100, + total_tax_amount: price * taxRate * 100, + }) + } + return order_lines } @@ -137,9 +150,9 @@ class KlarnaProviderService extends PaymentService { terms: this.options_.merchant_urls.terms, checkout: this.options_.merchant_urls.checkout, confirmation: this.options_.merchant_urls.confirmation, - push: `${this.backendUrl_}/klarna/hooks/push`, - shipping_option_update: `${this.backendUrl_}/klarna/hooks/shipping`, - address_update: `${this.backendUrl_}/klarna/hooks/address`, + push: `${this.backendUrl_}/klarna/push`, + shipping_option_update: `${this.backendUrl_}/klarna/shipping`, + address_update: `${this.backendUrl_}/klarna/address`, } @@ -151,8 +164,7 @@ class KlarnaProviderService extends PaymentService { const shipping_method = shippingOptions[0] order.selected_shipping_option = { id: shipping_method._id, - // TODO: Add shipping method name - name: shipping_method.provider_id, + name: shipping_method.name, price: shipping_method.price * (1 + tax_rate) * 100, tax_amount: shipping_method.price * tax_rate * 100, // Medusa tax rate of e.g. 0.25 (25%) needs to be 2500 in Klarna @@ -162,7 +174,7 @@ class KlarnaProviderService extends PaymentService { const shipping_method = cart.shipping_methods[0] order.selected_shipping_option = { id: shipping_method._id, - name: shipping_method.provider_id, + name: shipping_method.name, price: shipping_method.price * (1 + tax_rate) * 100, tax_amount: shipping_method.price * tax_rate * 100, tax_rate: tax_rate * 10000, @@ -173,7 +185,7 @@ class KlarnaProviderService extends PaymentService { order.shipping_options = shippingOptions.map((so) => ({ id: so._id, - name: so.provider_id, + name: so.name, price: so.price * (1 + tax_rate) * 100, tax_amount: so.price * tax_rate * 100, tax_rate: tax_rate * 10000, diff --git a/packages/medusa/src/models/schemas/shipping-method.js b/packages/medusa/src/models/schemas/shipping-method.js index 91d330fb62..92a195676e 100644 --- a/packages/medusa/src/models/schemas/shipping-method.js +++ b/packages/medusa/src/models/schemas/shipping-method.js @@ -1,6 +1,7 @@ import mongoose from "mongoose" export default new mongoose.Schema({ + name: { type: String, default: "" }, provider_id: { type: String, required: true }, profile_id: { type: String, required: true }, price: { type: Number, required: true },