Merge branch 'develop' of github.com:medusajs/medusa into develop
This commit is contained in:
@@ -1,59 +1,67 @@
|
||||
import { AbstractPaymentService } from "@medusajs/medusa";
|
||||
import { AbstractPaymentService } from "@medusajs/medusa"
|
||||
|
||||
class TestPayService extends AbstractPaymentService {
|
||||
static identifier = "test-pay";
|
||||
static identifier = "test-pay"
|
||||
|
||||
constructor(_) {
|
||||
super(_);
|
||||
super(_)
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return "authorized";
|
||||
return "authorized"
|
||||
}
|
||||
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([]);
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
async createPayment() {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async createPaymentNew() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async getPaymentData(sessionData) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePayment(sessionData, cart) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePaymentNew(sessionData, paymentInput) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async capturePayment(payment) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelPayment(payment) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export default TestPayService;
|
||||
export default TestPayService
|
||||
|
||||
@@ -33,6 +33,10 @@ class TestPayService extends AbstractPaymentService {
|
||||
return data
|
||||
}
|
||||
|
||||
async createPaymentNew(inputData) {
|
||||
return inputData
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return {}
|
||||
}
|
||||
@@ -59,6 +63,10 @@ class TestPayService extends AbstractPaymentService {
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePaymentNew(sessionData) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,15 @@ const bootstrapApp = async () => {
|
||||
}
|
||||
|
||||
const app = express()
|
||||
app.use((req, res, next) => {
|
||||
res.header("Access-Control-Allow-Origin", req.headers.origin)
|
||||
res.header("Access-Control-Allow-Methods", "*")
|
||||
res.header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept"
|
||||
)
|
||||
next()
|
||||
})
|
||||
|
||||
const dir = path.resolve(
|
||||
path.join(__dirname, "../../packages/medusa/src/loaders")
|
||||
|
||||
@@ -201,6 +201,10 @@ class AdyenService extends BaseService {
|
||||
return { cart_id: cart.id }
|
||||
}
|
||||
|
||||
async createPaymentNew(paymentInput) {
|
||||
return { resource_id: paymentInput.resource_id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Adyen payment. This is not supported by adyen, so we simply
|
||||
* return the current payment method data
|
||||
@@ -322,6 +326,10 @@ class AdyenService extends BaseService {
|
||||
return paymentData
|
||||
}
|
||||
|
||||
async updatePaymentNew(paymentData, details) {
|
||||
return paymentData
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional details
|
||||
* @param {object} paymentData - payment data
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { klarna_order_id } = req.query
|
||||
|
||||
function isPaymentCollection(id) {
|
||||
return id && id.startsWith("paycol")
|
||||
}
|
||||
|
||||
try {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
||||
@@ -11,10 +13,18 @@ export default async (req, res) => {
|
||||
klarna_order_id
|
||||
)
|
||||
|
||||
const cartId = klarnaOrder.merchant_data
|
||||
const order = await orderService.retrieveByCartId(cartId)
|
||||
const resourceId = klarnaOrder.merchant_data
|
||||
|
||||
await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order.id)
|
||||
if (isPaymentCollection(resourceId)) {
|
||||
await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id)
|
||||
} else {
|
||||
const order = await orderService.retrieveByCartId(resourceId)
|
||||
|
||||
await klarnaProviderService.acknowledgeOrder(
|
||||
klarnaOrder.order_id,
|
||||
order.id
|
||||
)
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
} catch (error) {
|
||||
|
||||
@@ -230,6 +230,84 @@ class KlarnaProviderService extends PaymentService {
|
||||
return order
|
||||
}
|
||||
|
||||
validateKlarnaOrderUrls(property) {
|
||||
const required = ["terms", "checkout", "confirmation"]
|
||||
|
||||
const isMissing = required.some((prop) => !this.options_[property]?.[prop])
|
||||
|
||||
if (isMissing) {
|
||||
throw new Error(
|
||||
`options.${property} is required to create a Klarna Order.\n` +
|
||||
`medusa-config.js file has to contain ${property} { ${required.join(
|
||||
", "
|
||||
)}}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
replaceStringWithPropertyValue(string, obj) {
|
||||
const keys = Object.keys(obj)
|
||||
for (const key of keys) {
|
||||
if (string.includes(`{${key}}`)) {
|
||||
string = string.replace(`{${key}}`, obj[key])
|
||||
}
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
async paymentInputToKlarnaOrder(paymentInput) {
|
||||
if (paymentInput.cart) {
|
||||
this.validateKlarnaOrderUrls("merchant_urls")
|
||||
return this.cartToKlarnaOrder(paymentInput.cart)
|
||||
}
|
||||
|
||||
this.validateKlarnaOrderUrls("payment_collection_urls")
|
||||
|
||||
let order = {
|
||||
// Custom id is stored, such that we can use it for hooks
|
||||
merchant_data: paymentInput.resource_id,
|
||||
locale: "en-US",
|
||||
}
|
||||
|
||||
const { currency_code, amount } = paymentInput
|
||||
|
||||
order.order_lines = [
|
||||
{
|
||||
name: "Payment Collection",
|
||||
quantity: 1,
|
||||
unit_price: amount,
|
||||
tax_rate: 0,
|
||||
total_amount: amount,
|
||||
total_tax_amount: 0,
|
||||
},
|
||||
]
|
||||
|
||||
// Defaults to Sweden
|
||||
order.purchase_country = "SE"
|
||||
|
||||
order.order_amount = amount
|
||||
order.order_tax_amount = 0
|
||||
order.purchase_currency = currency_code.toUpperCase()
|
||||
|
||||
order.merchant_urls = {
|
||||
terms: this.replaceStringWithPropertyValue(
|
||||
this.options_.payment_collection_urls.terms,
|
||||
paymentInput
|
||||
),
|
||||
checkout: this.replaceStringWithPropertyValue(
|
||||
this.options_.payment_collection_urls.checkout,
|
||||
paymentInput
|
||||
),
|
||||
confirmation: this.replaceStringWithPropertyValue(
|
||||
this.options_.payment_collection_urls.confirmation,
|
||||
paymentInput
|
||||
),
|
||||
push: `${this.backendUrl_}/klarna/push?klarna_order_id={checkout.order.id}`,
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for Klarna order.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
@@ -251,7 +329,7 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Stripe PaymentIntent.
|
||||
* Creates Klarna PaymentIntent.
|
||||
* @param {string} cart - the cart to create a payment for
|
||||
* @param {number} amount - the amount to create a payment for
|
||||
* @returns {string} id of payment intent
|
||||
@@ -271,6 +349,21 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
async createPaymentNew(paymentInput) {
|
||||
try {
|
||||
const order = await this.paymentInputToKlarnaOrder(paymentInput)
|
||||
|
||||
const klarnaPayment = await this.klarna_
|
||||
.post(this.klarnaOrderUrl_, order)
|
||||
.then(({ data }) => data)
|
||||
|
||||
return klarnaPayment
|
||||
} catch (error) {
|
||||
this.logger_.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Klarna Order.
|
||||
* @param {string} cart - the cart to retrieve order for
|
||||
@@ -338,18 +431,20 @@ class KlarnaProviderService extends PaymentService {
|
||||
* @param {string} klarnaOrderId - id of the order to acknowledge
|
||||
* @returns {string} id of acknowledged order
|
||||
*/
|
||||
async acknowledgeOrder(klarnaOrderId, orderId) {
|
||||
async acknowledgeOrder(klarnaOrderId, orderId = null) {
|
||||
try {
|
||||
await this.klarna_.post(
|
||||
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/acknowledge`
|
||||
)
|
||||
|
||||
await this.klarna_.patch(
|
||||
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/merchant-references`,
|
||||
{
|
||||
merchant_reference1: orderId,
|
||||
}
|
||||
)
|
||||
if (orderId !== null) {
|
||||
await this.klarna_.patch(
|
||||
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/merchant-references`,
|
||||
{
|
||||
merchant_reference1: orderId,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return klarnaOrderId
|
||||
} catch (error) {
|
||||
@@ -408,6 +503,22 @@ class KlarnaProviderService extends PaymentService {
|
||||
return paymentData
|
||||
}
|
||||
|
||||
async updatePaymentNew(paymentData, paymentInput) {
|
||||
if (paymentInput.amount !== paymentData.order_amount) {
|
||||
const order = await this.paymentInputToKlarnaOrder(paymentInput)
|
||||
return this.klarna_
|
||||
.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order)
|
||||
.then(({ data }) => data)
|
||||
.catch(async (_) => {
|
||||
return this.klarna_
|
||||
.post(this.klarnaOrderUrl_, order)
|
||||
.then(({ data }) => data)
|
||||
})
|
||||
}
|
||||
|
||||
return paymentData
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures Klarna order.
|
||||
* @param {Object} paymentData - payment method data from cart
|
||||
|
||||
@@ -26,6 +26,10 @@ class ManualPaymentService extends PaymentService {
|
||||
return { status: "pending" }
|
||||
}
|
||||
|
||||
async createPaymentNew() {
|
||||
return { status: "pending" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves payment
|
||||
* @param {object} data - the data of the payment to retrieve
|
||||
@@ -52,6 +56,10 @@ class ManualPaymentService extends PaymentService {
|
||||
return sessionData.data
|
||||
}
|
||||
|
||||
async updatePaymentNew(sessionData) {
|
||||
return sessionData.data
|
||||
}
|
||||
|
||||
/**
|
||||
.
|
||||
* @param {object} sessionData - payment session data.
|
||||
|
||||
@@ -21,21 +21,11 @@ export default async (req, res) => {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const body = req.body
|
||||
const authId = body.resource.id
|
||||
const auth = await paypalService.retrieveAuthorization(authId)
|
||||
|
||||
const order = await paypalService.retrieveOrderFromAuth(auth)
|
||||
|
||||
const purchaseUnit = order.purchase_units[0]
|
||||
const cartId = purchaseUnit.custom_id
|
||||
|
||||
if (!cartId) {
|
||||
res.sendStatus(200)
|
||||
return
|
||||
}
|
||||
function isPaymentCollection(id) {
|
||||
return id && id.startsWith("paycol")
|
||||
}
|
||||
|
||||
async function autorizeCart(req, cartId) {
|
||||
const manager = req.scope.resolve("manager")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const swapService = req.scope.resolve("swapService")
|
||||
@@ -78,6 +68,42 @@ export default async (req, res) => {
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function autorizePaymentCollection(req, payColId) {
|
||||
const manager = req.scope.resolve("manager")
|
||||
const paymentCollectionService = req.scope.resolve(
|
||||
"paymentCollectonService"
|
||||
)
|
||||
|
||||
await manager.transaction(async (m) => {
|
||||
const payCol = await paymentCollectionService
|
||||
.withTransaction(m)
|
||||
.retrieve(payColId)
|
||||
})
|
||||
// TODO: complete authorization
|
||||
}
|
||||
|
||||
try {
|
||||
const body = req.body
|
||||
const authId = body.resource.id
|
||||
const auth = await paypalService.retrieveAuthorization(authId)
|
||||
|
||||
const order = await paypalService.retrieveOrderFromAuth(auth)
|
||||
|
||||
const purchaseUnit = order.purchase_units[0]
|
||||
const customId = purchaseUnit.custom_id
|
||||
|
||||
if (!customId) {
|
||||
res.sendStatus(200)
|
||||
return
|
||||
}
|
||||
|
||||
if (isPaymentCollection(customId)) {
|
||||
await autorizePaymentCollection(req, customId)
|
||||
} else {
|
||||
await autorizeCart(req, customId)
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
} catch (err) {
|
||||
|
||||
@@ -118,6 +118,34 @@ class PayPalProviderService extends PaymentService {
|
||||
return { id: res.result.id }
|
||||
}
|
||||
|
||||
async createPaymentNew(paymentInput) {
|
||||
const { resource_id, currency_code, amount } = paymentInput
|
||||
|
||||
const request = new PayPal.orders.OrdersCreateRequest()
|
||||
request.requestBody({
|
||||
intent: "AUTHORIZE",
|
||||
application_context: {
|
||||
shipping_preference: "NO_SHIPPING",
|
||||
},
|
||||
purchase_units: [
|
||||
{
|
||||
custom_id: resource_id,
|
||||
amount: {
|
||||
currency_code: currency_code.toUpperCase(),
|
||||
value: roundToTwo(
|
||||
humanizeAmount(amount, currency_code),
|
||||
currency_code
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const res = await this.paypal_.execute(request)
|
||||
|
||||
return { id: res.result.id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a PayPal order.
|
||||
* @param {object} data - the data stored with the payment
|
||||
@@ -216,6 +244,35 @@ class PayPalProviderService extends PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
async updatePaymentNew(sessionData, paymentInput) {
|
||||
try {
|
||||
const { currency_code, amount } = paymentInput
|
||||
|
||||
const request = new PayPal.orders.OrdersPatchRequest(sessionData.id)
|
||||
request.requestBody([
|
||||
{
|
||||
op: "replace",
|
||||
path: "/purchase_units/@reference_id=='default'",
|
||||
value: {
|
||||
amount: {
|
||||
currency_code: currency_code.toUpperCase(),
|
||||
value: roundToTwo(
|
||||
humanizeAmount(amount, currency_code),
|
||||
currency_code
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
await this.paypal_.execute(request)
|
||||
|
||||
return sessionData
|
||||
} catch (error) {
|
||||
return this.createPaymentNew(paymentInput)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not suported
|
||||
*/
|
||||
|
||||
@@ -10,40 +10,53 @@ export default async (req, res) => {
|
||||
return
|
||||
}
|
||||
|
||||
const paymentIntent = event.data.object
|
||||
|
||||
const manager = req.scope.resolve("manager")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const cartId = paymentIntent.metadata.cart_id
|
||||
const order = await orderService
|
||||
.retrieveByCartId(cartId)
|
||||
.catch(() => undefined)
|
||||
|
||||
// handle payment intent events
|
||||
switch (event.type) {
|
||||
case "payment_intent.succeeded":
|
||||
if (order && order.payment_status !== "captured") {
|
||||
await manager.transaction(async (manager) => {
|
||||
await orderService.withTransaction(manager).capturePayment(order.id)
|
||||
})
|
||||
}
|
||||
break
|
||||
case "payment_intent.amount_capturable_updated":
|
||||
if (!order) {
|
||||
await manager.transaction(async (manager) => {
|
||||
const cartServiceTx = cartService.withTransaction(manager)
|
||||
await cartServiceTx.setPaymentSession(cartId, "stripe")
|
||||
await cartServiceTx.authorizePayment(cartId)
|
||||
await orderService.withTransaction(manager).createFromCart(cartId)
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
res.sendStatus(204)
|
||||
return
|
||||
function isPaymentCollection(id) {
|
||||
return id && id.startsWith("paycol")
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
async function handleCartPayments(event, req, res, cartId) {
|
||||
const manager = req.scope.resolve("manager")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const order = await orderService
|
||||
.retrieveByCartId(cartId)
|
||||
.catch(() => undefined)
|
||||
|
||||
// handle payment intent events
|
||||
switch (event.type) {
|
||||
case "payment_intent.succeeded":
|
||||
if (order && order.payment_status !== "captured") {
|
||||
await manager.transaction(async (manager) => {
|
||||
await orderService.withTransaction(manager).capturePayment(order.id)
|
||||
})
|
||||
}
|
||||
break
|
||||
case "payment_intent.amount_capturable_updated":
|
||||
if (!order) {
|
||||
await manager.transaction(async (manager) => {
|
||||
const cartServiceTx = cartService.withTransaction(manager)
|
||||
await cartServiceTx.setPaymentSession(cartId, "stripe")
|
||||
await cartServiceTx.authorizePayment(cartId)
|
||||
await orderService.withTransaction(manager).createFromCart(cartId)
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
res.sendStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
const paymentIntent = event.data.object
|
||||
const cartId = paymentIntent.metadata.cart_id
|
||||
const resourceId = paymentIntent.metadata.resource_id
|
||||
|
||||
if (isPaymentCollection(resourceId)) {
|
||||
// TODO: handle payment collection
|
||||
} else {
|
||||
await handleCartPayments(event, req, res, resourceId ?? cartId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import Stripe from "stripe"
|
||||
import { AbstractPaymentService, PaymentSessionData } from "@medusajs/medusa"
|
||||
|
||||
class StripeBase extends AbstractPaymentService {
|
||||
static identifier = null
|
||||
|
||||
constructor(
|
||||
{
|
||||
stripeProviderService,
|
||||
customerService,
|
||||
totalsService,
|
||||
regionService,
|
||||
manager,
|
||||
},
|
||||
options,
|
||||
paymentMethodTypes
|
||||
) {
|
||||
super(
|
||||
{
|
||||
stripeProviderService,
|
||||
customerService,
|
||||
totalsService,
|
||||
regionService,
|
||||
manager,
|
||||
},
|
||||
options
|
||||
)
|
||||
/** @private @const {string[]} */
|
||||
this.paymentMethodTypes = paymentMethodTypes
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
/** @private @const {EntityManager} */
|
||||
this.manager_ = manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentSessionData) {
|
||||
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @return {Promise<Data[]>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
return await this.stripeProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.createCustomer(customer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {Cart} cart - cart to create a payment for
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const intentRequest = {
|
||||
payment_method_types: this.paymentMethodTypes,
|
||||
capture_method: "automatic",
|
||||
}
|
||||
|
||||
return await this.stripeProviderService_.createPayment(cart, intentRequest)
|
||||
}
|
||||
|
||||
async createPaymentNew(paymentInput) {
|
||||
const intentRequest = {
|
||||
payment_method_types: this.paymentMethodTypes,
|
||||
capture_method: "automatic",
|
||||
}
|
||||
|
||||
return await this.stripeProviderService_.createPaymentNew(
|
||||
paymentInput,
|
||||
intentRequest
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||
* @return {Promise<Data>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(paymentData) {
|
||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(paymentSession) {
|
||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {PaymentSession} paymentSession - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
||||
*/
|
||||
async authorizePayment(paymentSession, context = {}) {
|
||||
return await this.stripeProviderService_.authorizePayment(
|
||||
paymentSession,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentData(paymentSessionData, data) {
|
||||
return await this.stripeProviderService_.updatePaymentData(
|
||||
paymentSessionData,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
||||
* @param {Cart} cart
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(paymentSessionData, cart) {
|
||||
return await this.stripeProviderService_.updatePayment(
|
||||
paymentSessionData,
|
||||
cart
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentNew(paymentSessionData, paymentInput) {
|
||||
return await this.stripeProviderService_.updatePaymentNew(
|
||||
paymentSessionData,
|
||||
paymentInput
|
||||
)
|
||||
}
|
||||
|
||||
async deletePayment(paymentSession) {
|
||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @return {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
paymentIntentId,
|
||||
customerId
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
return await this.stripeProviderService_.capturePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @param {number} refundAmount - amount to refund
|
||||
* @return {Promise<PaymentData>} refunded payment intent
|
||||
*/
|
||||
async refundPayment(payment, refundAmount) {
|
||||
return await this.stripeProviderService_.refundPayment(
|
||||
payment,
|
||||
refundAmount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
return await this.stripeProviderService_.cancelPayment(payment)
|
||||
}
|
||||
}
|
||||
|
||||
export default StripeBase
|
||||
@@ -1,7 +1,6 @@
|
||||
import Stripe from "stripe"
|
||||
import { AbstractPaymentService, PaymentSessionData } from "@medusajs/medusa"
|
||||
import StripeBase from "../helpers/stripe-base"
|
||||
|
||||
class BancontactProviderService extends AbstractPaymentService {
|
||||
class BancontactProviderService extends StripeBase {
|
||||
static identifier = "stripe-bancontact"
|
||||
|
||||
constructor(
|
||||
@@ -22,245 +21,9 @@ class BancontactProviderService extends AbstractPaymentService {
|
||||
regionService,
|
||||
manager,
|
||||
},
|
||||
options
|
||||
options,
|
||||
["bancontact"]
|
||||
)
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
/** @private @const {EntityManager} */
|
||||
this.manager_ = manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentSessionData) {
|
||||
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @return {Promise<Data[]>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
return await this.stripeProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.createCustomer(customer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {Cart} cart - cart to create a payment for
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const { customer_id, region_id, email } = cart
|
||||
const region = await this.regionService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(region_id)
|
||||
const { currency_code } = region
|
||||
|
||||
const amount = await this.totalsService_
|
||||
.withTransaction(this.manager_)
|
||||
.getTotal(cart)
|
||||
|
||||
const intentRequest = {
|
||||
amount: Math.round(amount),
|
||||
description:
|
||||
cart?.context?.payment_description ?? this.options?.payment_description,
|
||||
currency: currency_code,
|
||||
payment_method_types: ["bancontact"],
|
||||
capture_method: "automatic",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(customer_id)
|
||||
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||
* @return {Promise<Data>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(paymentData) {
|
||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(paymentSession) {
|
||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {PaymentSession} paymentSession - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
||||
*/
|
||||
async authorizePayment(paymentSession, context = {}) {
|
||||
return await this.stripeProviderService_.authorizePayment(
|
||||
paymentSession,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentData(paymentSessionData, data) {
|
||||
return await this.stripeProviderService_.updatePaymentData(
|
||||
paymentSessionData,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
||||
* @param {Cart} cart
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(paymentSessionData, cart) {
|
||||
try {
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== paymentSessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
} else {
|
||||
if (
|
||||
cart.total &&
|
||||
paymentSessionData.amount === Math.round(cart.total)
|
||||
) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
||||
amount: Math.round(cart.total),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(paymentSession) {
|
||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @return {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
paymentIntentId,
|
||||
customerId
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
return await this.stripeProviderService_.capturePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @param {number} refundAmount - amount to refund
|
||||
* @return {Promise<PaymentData>} refunded payment intent
|
||||
*/
|
||||
async refundPayment(payment, refundAmount) {
|
||||
return await this.stripeProviderService_.refundPayment(
|
||||
payment,
|
||||
refundAmount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
return await this.stripeProviderService_.cancelPayment(payment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Stripe from "stripe"
|
||||
import { AbstractPaymentService, PaymentSessionStatus } from "@medusajs/medusa"
|
||||
import StripeBase from "../helpers/stripe-base"
|
||||
|
||||
class BlikProviderService extends AbstractPaymentService {
|
||||
class BlikProviderService extends StripeBase {
|
||||
static identifier = "stripe-blik"
|
||||
|
||||
constructor(
|
||||
@@ -22,242 +21,9 @@ class BlikProviderService extends AbstractPaymentService {
|
||||
regionService,
|
||||
manager,
|
||||
},
|
||||
options
|
||||
options,
|
||||
["blik"]
|
||||
)
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
this.manager_ = manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentSessionData) {
|
||||
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @return {Promise<Data[]>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
return await this.stripeProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.createCustomer(customer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {Cart} cart - cart to create a payment for
|
||||
* @returns {PaymentSessionData} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const { customer_id, region_id, email } = cart
|
||||
const region = await this.regionService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(region_id)
|
||||
const { currency_code } = region
|
||||
|
||||
const amount = await this.totalsService_
|
||||
.withTransaction(this.manager_)
|
||||
.getTotal(cart)
|
||||
|
||||
const intentRequest = {
|
||||
amount: Math.round(amount),
|
||||
currency: currency_code,
|
||||
payment_method_types: ["blik"],
|
||||
capture_method: "automatic",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(customer_id)
|
||||
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||
* @return {Promise<Data>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(paymentData) {
|
||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(paymentSession) {
|
||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {PaymentSession} paymentSession - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
||||
*/
|
||||
async authorizePayment(paymentSession, context = {}) {
|
||||
return await this.stripeProviderService_.authorizePayment(
|
||||
paymentSession,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentData(paymentSessionData, data) {
|
||||
return await this.stripeProviderService_.updatePaymentData(
|
||||
paymentSessionData,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
||||
* @param {Cart} cart
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(paymentSessionData, cart) {
|
||||
try {
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== paymentSessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
} else {
|
||||
if (
|
||||
cart.total &&
|
||||
paymentSessionData.amount === Math.round(cart.total)
|
||||
) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
||||
amount: Math.round(cart.total),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(paymentSession) {
|
||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @return {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
paymentIntentId,
|
||||
customerId
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
return await this.stripeProviderService_.capturePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @param {number} refundAmount - amount to refund
|
||||
* @return {Promise<PaymentData>} refunded payment intent
|
||||
*/
|
||||
async refundPayment(payment, refundAmount) {
|
||||
return await this.stripeProviderService_.refundPayment(
|
||||
payment,
|
||||
refundAmount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
return await this.stripeProviderService_.cancelPayment(payment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Stripe from "stripe"
|
||||
import { AbstractPaymentService, PaymentSessionStatus } from "@medusajs/medusa"
|
||||
import StripeBase from "../helpers/stripe-base"
|
||||
|
||||
class GiropayProviderService extends AbstractPaymentService {
|
||||
class GiropayProviderService extends StripeBase {
|
||||
static identifier = "stripe-giropay"
|
||||
|
||||
constructor(
|
||||
@@ -22,245 +21,9 @@ class GiropayProviderService extends AbstractPaymentService {
|
||||
regionService,
|
||||
manager,
|
||||
},
|
||||
options
|
||||
options,
|
||||
["giropay"]
|
||||
)
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
/** @private @const {EntityManager} */
|
||||
this.manager_ = manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentSessionData) {
|
||||
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {Customer} customer - customer to fetch saved cards for
|
||||
* @return {Promise<Data[]>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
return await this.stripeProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.createCustomer(customer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {Cart} cart - cart to create a payment for
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const { customer_id, region_id, email } = cart
|
||||
const region = await this.regionService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(region_id)
|
||||
const { currency_code } = region
|
||||
|
||||
const amount = await this.totalsService_
|
||||
.withTransaction(this.manager_)
|
||||
.getTotal(cart)
|
||||
|
||||
const intentRequest = {
|
||||
amount: Math.round(amount),
|
||||
description:
|
||||
cart?.context?.payment_description ?? this.options?.payment_description,
|
||||
currency: currency_code,
|
||||
payment_method_types: ["giropay"],
|
||||
capture_method: "automatic",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(customer_id)
|
||||
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||
* @return {Promise<Data>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(paymentData) {
|
||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(paymentSession) {
|
||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {PaymentSession} paymentSession - payment session data
|
||||
* @param {Data} context - properties relevant to current context
|
||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
||||
*/
|
||||
async authorizePayment(paymentSession, context = {}) {
|
||||
return await this.stripeProviderService_.authorizePayment(
|
||||
paymentSession,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentData(paymentSessionData, data) {
|
||||
return await this.stripeProviderService_.updatePaymentData(
|
||||
paymentSessionData,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
||||
* @param {Cart} cart
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(paymentSessionData, cart) {
|
||||
try {
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== paymentSessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
} else {
|
||||
if (
|
||||
cart.total &&
|
||||
paymentSessionData.amount === Math.round(cart.total)
|
||||
) {
|
||||
return paymentSessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
||||
amount: Math.round(cart.total),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(paymentSession) {
|
||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @return {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
paymentIntentId,
|
||||
customerId
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
return await this.stripeProviderService_.capturePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @param {number} refundAmount - amount to refund
|
||||
* @return {Promise<PaymentData>} refunded payment intent
|
||||
*/
|
||||
async refundPayment(payment, refundAmount) {
|
||||
return await this.stripeProviderService_.refundPayment(
|
||||
payment,
|
||||
refundAmount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
return await this.stripeProviderService_.cancelPayment(payment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Stripe from "stripe"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
import StripeBase from "../helpers/stripe-base"
|
||||
|
||||
class IdealProviderService extends PaymentService {
|
||||
class IdealProviderService extends StripeBase {
|
||||
static identifier = "stripe-ideal"
|
||||
|
||||
constructor(
|
||||
@@ -22,242 +21,9 @@ class IdealProviderService extends PaymentService {
|
||||
regionService,
|
||||
manager,
|
||||
},
|
||||
options
|
||||
options,
|
||||
["ideal"]
|
||||
)
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
this.manager_ = manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
return await this.stripeProviderService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @return {Promise<Data[]>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
return await this.stripeProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.createCustomer(customer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {Cart} cart - cart to create a payment for
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const { customer_id, region_id, email } = cart
|
||||
const region = await this.regionService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(region_id)
|
||||
const { currency_code } = region
|
||||
|
||||
const amount = await this.totalsService_
|
||||
.withTransaction(this.manager_)
|
||||
.getTotal(cart)
|
||||
|
||||
const intentRequest = {
|
||||
amount: Math.round(amount),
|
||||
description:
|
||||
cart?.context?.payment_description ??
|
||||
this.options_?.payment_description,
|
||||
currency: currency_code,
|
||||
payment_method_types: ["ideal"],
|
||||
capture_method: "automatic",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(customer_id)
|
||||
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||
* @return {Promise<Data>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(paymentData) {
|
||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(paymentSession) {
|
||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {PaymentSession} paymentSession - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
||||
*/
|
||||
async authorizePayment(paymentSession, context = {}) {
|
||||
return await this.stripeProviderService_.authorizePayment(
|
||||
paymentSession,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentData(paymentSessionData, data) {
|
||||
return await this.stripeProviderService_.updatePaymentData(
|
||||
paymentSessionData,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
||||
* @param {Cart} cart
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(sessionData, cart) {
|
||||
try {
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== sessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
} else {
|
||||
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
||||
amount: Math.round(cart.total),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(paymentSession) {
|
||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @return {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
paymentIntentId,
|
||||
customerId
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
return await this.stripeProviderService_.capturePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @param {number} refundAmount - amount to refund
|
||||
* @return {Promise<PaymentData>} refunded payment intent
|
||||
*/
|
||||
async refundPayment(payment, refundAmount) {
|
||||
return await this.stripeProviderService_.refundPayment(
|
||||
payment,
|
||||
refundAmount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
return await this.stripeProviderService_.cancelPayment(payment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class StripeProviderService extends AbstractPaymentService {
|
||||
* @param {Cart} cart - cart to create a payment for
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
async createPayment(cart, intentRequestData = {}) {
|
||||
const { customer_id, region_id, email } = cart
|
||||
const { currency_code } = await this.regionService_
|
||||
.withTransaction(this.manager_)
|
||||
@@ -143,9 +143,10 @@ class StripeProviderService extends AbstractPaymentService {
|
||||
this.options_?.payment_description,
|
||||
amount: Math.round(amount),
|
||||
currency: currency_code,
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
setup_future_usage: "on_session",
|
||||
capture_method: this.options_.capture ? "automatic" : "manual",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
...intentRequestData,
|
||||
}
|
||||
|
||||
if (this.options_?.automatic_payment_methods) {
|
||||
@@ -178,6 +179,44 @@ class StripeProviderService extends AbstractPaymentService {
|
||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||
}
|
||||
|
||||
async createPaymentNew(paymentInput, intentRequestData = {}) {
|
||||
const { customer, currency_code, amount, resource_id, cart } = paymentInput
|
||||
const { id: customer_id, email } = customer
|
||||
|
||||
let intentRequest = {
|
||||
description:
|
||||
cart?.context?.payment_description ??
|
||||
this.options_?.payment_description,
|
||||
amount: Math.round(amount),
|
||||
currency: currency_code,
|
||||
metadata: { resource_id },
|
||||
setup_future_usage: "on_session",
|
||||
capture_method: this.options_.capture ? "automatic" : "manual",
|
||||
...intentRequestData,
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||
@@ -213,7 +252,6 @@ class StripeProviderService extends AbstractPaymentService {
|
||||
*/
|
||||
async authorizePayment(paymentSession, context = {}) {
|
||||
const stat = await this.getStatus(paymentSession.data)
|
||||
|
||||
try {
|
||||
return { data: paymentSession.data, status: stat }
|
||||
} catch (error) {
|
||||
@@ -242,7 +280,7 @@ class StripeProviderService extends AbstractPaymentService {
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== sessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
return await this.createPayment(cart)
|
||||
} else {
|
||||
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
||||
return sessionData
|
||||
@@ -257,6 +295,26 @@ class StripeProviderService extends AbstractPaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
async updatePaymentNew(paymentSessionData, paymentInput) {
|
||||
try {
|
||||
const stripeId = paymentInput.customer?.metadata?.stripe_id
|
||||
|
||||
if (stripeId !== paymentInput.customer_id) {
|
||||
return await this.createPaymentNew(paymentInput)
|
||||
} else {
|
||||
if (paymentSessionData.amount === Math.round(paymentInput.amount)) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
||||
amount: Math.round(paymentInput.amount),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
try {
|
||||
const { id } = payment.data
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Stripe from "stripe"
|
||||
import { AbstractPaymentService, PaymentSessionStatus } from "@medusajs/medusa"
|
||||
import StripeBase from "../helpers/stripe-base"
|
||||
|
||||
class Przelewy24ProviderService extends AbstractPaymentService {
|
||||
class Przelewy24ProviderService extends StripeBase {
|
||||
static identifier = "stripe-przelewy24"
|
||||
|
||||
constructor(
|
||||
@@ -22,239 +21,9 @@ class Przelewy24ProviderService extends AbstractPaymentService {
|
||||
regionService,
|
||||
manager,
|
||||
},
|
||||
options
|
||||
options,
|
||||
["p24"]
|
||||
)
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
this.manager_ = manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
return await this.stripeProviderService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @return {Promise<Data[]>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @return {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
return await this.stripeProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.createCustomer(customer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {object} cart - cart to create a payment for
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const { customer_id, region_id, email } = cart
|
||||
const region = await this.regionService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(region_id)
|
||||
const { currency_code } = region
|
||||
|
||||
const amount = await this.totalsService_
|
||||
.withTransaction(this.manager_)
|
||||
.getTotal(cart)
|
||||
|
||||
const intentRequest = {
|
||||
amount: Math.round(amount),
|
||||
currency: currency_code,
|
||||
payment_method_types: ["p24"],
|
||||
capture_method: "automatic",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(customer_id)
|
||||
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||
* @return {Promise<Data>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(paymentData) {
|
||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(paymentSession) {
|
||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {PaymentSession} paymentSession - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
||||
*/
|
||||
async authorizePayment(paymentSession, context = {}) {
|
||||
return await this.stripeProviderService_.authorizePayment(
|
||||
paymentSession,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentData(paymentSessionData, data) {
|
||||
return await this.stripeProviderService_.updatePaymentData(
|
||||
paymentSessionData,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
||||
* @param {Cart} cart
|
||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(sessionData, cart) {
|
||||
try {
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== sessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
} else {
|
||||
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
||||
amount: Math.round(cart.total),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(paymentSession) {
|
||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @return {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
paymentIntentId,
|
||||
customerId
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
return await this.stripeProviderService_.capturePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @param {number} refundAmount - amount to refund
|
||||
* @return {Promise<PaymentData>} refunded payment intent
|
||||
*/
|
||||
async refundPayment(payment, refundAmount) {
|
||||
return await this.stripeProviderService_.refundPayment(
|
||||
payment,
|
||||
refundAmount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {Payment} payment - payment method data from cart
|
||||
* @return {Promise<PaymentData>} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
return await this.stripeProviderService_.cancelPayment(payment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
PaymentSessionStatus,
|
||||
} from "../models"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
import { PaymentProviderDataInput } from "../types/payment-collection"
|
||||
|
||||
export type Data = Record<string, unknown>
|
||||
export type PaymentData = Data
|
||||
@@ -76,6 +77,9 @@ export abstract class AbstractPaymentService
|
||||
): Promise<PaymentSessionData>
|
||||
|
||||
public abstract createPayment(cart: Cart): Promise<PaymentSessionData>
|
||||
public abstract createPaymentNew(
|
||||
paymentInput: PaymentProviderDataInput
|
||||
): Promise<PaymentSessionData>
|
||||
|
||||
public abstract retrievePayment(paymentData: PaymentData): Promise<Data>
|
||||
|
||||
@@ -84,6 +88,11 @@ export abstract class AbstractPaymentService
|
||||
cart: Cart
|
||||
): Promise<PaymentSessionData>
|
||||
|
||||
public abstract updatePaymentNew(
|
||||
paymentSessionData: PaymentSessionData,
|
||||
paymentInput: PaymentProviderDataInput
|
||||
): Promise<PaymentSessionData>
|
||||
|
||||
public abstract authorizePayment(
|
||||
paymentSession: PaymentSession,
|
||||
context: Data
|
||||
|
||||
@@ -27,6 +27,7 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
description text NULL,
|
||||
amount integer NOT NULL,
|
||||
authorized_amount integer NULL,
|
||||
captured_amount integer NULL,
|
||||
refunded_amount integer NULL,
|
||||
region_id character varying NOT NULL,
|
||||
currency_code character varying NOT NULL,
|
||||
@@ -71,6 +72,12 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
ALTER TABLE "order_edit" ADD CONSTRAINT "FK_order_edit_payment_collection_id" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
ALTER TABLE payment_session ADD COLUMN payment_authorized_at timestamp WITH time zone NULL;
|
||||
ALTER TABLE payment_session ADD COLUMN amount integer NULL;
|
||||
ALTER TABLE payment_session ALTER COLUMN cart_id DROP NOT NULL;
|
||||
|
||||
ALTER TABLE refund ADD COLUMN payment_id character varying NULL;
|
||||
CREATE INDEX "IDX_refund_payment_id" ON "refund" ("payment_id");
|
||||
ALTER TABLE "refund" ADD CONSTRAINT "FK_refund_payment_id" FOREIGN KEY ("payment_id") REFERENCES "payment"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
`)
|
||||
|
||||
// Add missing indexes
|
||||
@@ -85,6 +92,12 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
DROP INDEX "IDX_order_edit_payment_collection_id";
|
||||
ALTER TABLE order_edit DROP CONSTRAINT "FK_order_edit_payment_collection_id";
|
||||
|
||||
DROP INDEX "IDX_refund_payment_id";
|
||||
ALTER TABLE refund DROP CONSTRAINT "FK_refund_payment_id";
|
||||
|
||||
ALTER TABLE payment_collection DROP CONSTRAINT "FK_payment_collection_region_id";
|
||||
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_collection_id";
|
||||
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_session_id";
|
||||
@@ -92,6 +105,9 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
ALTER TABLE payment_collection_payments DROP CONSTRAINT "FK_payment_collection_payments_payment_id";
|
||||
ALTER TABLE order_edit DROP COLUMN payment_collection_id;
|
||||
ALTER TABLE payment_session DROP COLUMN payment_authorized_at;
|
||||
ALTER TABLE payment_session DROP COLUMN amount;
|
||||
ALTER TABLE payment_session ALTER COLUMN cart_id SET NOT NULL;
|
||||
ALTER TABLE refund DROP COLUMN payment_id;
|
||||
|
||||
DROP TABLE payment_collection;
|
||||
DROP TABLE payment_collection_sessions;
|
||||
@@ -99,10 +115,6 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
|
||||
DROP TYPE "PAYMENT_COLLECTION_TYPE_ENUM";
|
||||
DROP TYPE "PAYMENT_COLLECTION_STATUS_ENUM";
|
||||
|
||||
DROP INDEX "IDX_order_edit_payment_collection_id";
|
||||
ALTER TABLE order_edit DROP CONSTRAINT "FK_order_edit_payment_collection_id";
|
||||
|
||||
`)
|
||||
|
||||
await queryRunner.query(`
|
||||
|
||||
@@ -50,6 +50,9 @@ export class PaymentCollection extends SoftDeletableEntity {
|
||||
@Column({ type: "int", nullable: true })
|
||||
authorized_amount: number
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
captured_amount: number
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
refunded_amount: number
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export enum PaymentSessionStatus {
|
||||
@Entity()
|
||||
export class PaymentSession extends BaseEntity {
|
||||
@Index()
|
||||
@Column()
|
||||
@Column({ nullable: true })
|
||||
cart_id: string
|
||||
|
||||
@ManyToOne(() => Cart, (cart) => cart.payment_sessions)
|
||||
@@ -51,6 +51,11 @@ export class PaymentSession extends BaseEntity {
|
||||
@Column({ nullable: true })
|
||||
idempotency_key: string
|
||||
|
||||
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||
Column({ type: "integer", nullable: true }),
|
||||
])
|
||||
amount: number
|
||||
|
||||
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||
Column({ type: resolveDbType("timestamptz"), nullable: true }),
|
||||
])
|
||||
|
||||
@@ -5,12 +5,16 @@ import {
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
} from "typeorm"
|
||||
|
||||
import { BaseEntity } from "../interfaces/models/base-entity"
|
||||
import { DbAwareColumn } from "../utils/db-aware-column"
|
||||
import { Order } from "./order"
|
||||
import { generateEntityId } from "../utils/generate-entity-id"
|
||||
import { Payment } from "./payment"
|
||||
import { FeatureFlagDecorators } from "../utils/feature-flag-decorators"
|
||||
import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing"
|
||||
|
||||
export enum RefundReason {
|
||||
DISCOUNT = "discount",
|
||||
@@ -23,13 +27,25 @@ export enum RefundReason {
|
||||
@Entity()
|
||||
export class Refund extends BaseEntity {
|
||||
@Index()
|
||||
@Column()
|
||||
@Column({ nullable: true })
|
||||
order_id: string
|
||||
|
||||
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||
Index(),
|
||||
Column({ nullable: true }),
|
||||
])
|
||||
payment_id: string
|
||||
|
||||
@ManyToOne(() => Order, (order) => order.payments)
|
||||
@JoinColumn({ name: "order_id" })
|
||||
order: Order
|
||||
|
||||
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||
OneToOne(() => Payment, { nullable: true }),
|
||||
JoinColumn({ name: "payment_id" }),
|
||||
])
|
||||
payment: Payment
|
||||
|
||||
@Column({ type: "int" })
|
||||
amount: number
|
||||
|
||||
|
||||
@@ -1,6 +1,72 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { PaymentCollection } from "./../models/payment-collection"
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { FindConfig } from "../types/common"
|
||||
import { PaymentSession } from "../models"
|
||||
|
||||
@EntityRepository(PaymentCollection)
|
||||
// eslint-disable-next-line max-len
|
||||
export class PaymentCollectionRepository extends Repository<PaymentCollection> {}
|
||||
export class PaymentCollectionRepository extends Repository<PaymentCollection> {
|
||||
async getPaymentCollectionIdBySessionId(
|
||||
sessionId: string,
|
||||
config: FindConfig<PaymentCollection> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
const paymentCollection = await this.find({
|
||||
join: {
|
||||
alias: "payment_col",
|
||||
innerJoin: { payment_sessions: "payment_col.payment_sessions" },
|
||||
},
|
||||
where: (qb) => {
|
||||
qb.where(
|
||||
"payment_col_payment_sessions.payment_session_id = :sessionId",
|
||||
{ sessionId }
|
||||
)
|
||||
},
|
||||
relations: config.relations,
|
||||
select: config.select,
|
||||
})
|
||||
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Payment collection related to Payment Session id ${sessionId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return paymentCollection[0]
|
||||
}
|
||||
|
||||
async getPaymentCollectionIdByPaymentId(
|
||||
paymentId: string,
|
||||
config: FindConfig<PaymentCollection> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
const paymentCollection = await this.find({
|
||||
join: {
|
||||
alias: "payment_col",
|
||||
innerJoin: { payments: "payment_col.payments" },
|
||||
},
|
||||
where: (qb) => {
|
||||
qb.where("payment_col_payments.payment_id = :paymentId", { paymentId })
|
||||
},
|
||||
relations: config.relations,
|
||||
select: config.select,
|
||||
})
|
||||
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Payment collection related to Payment id ${paymentId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return paymentCollection[0]
|
||||
}
|
||||
|
||||
async deleteMultiple(ids: string[]): Promise<void> {
|
||||
await this.createQueryBuilder()
|
||||
.delete()
|
||||
.from(PaymentSession)
|
||||
.where("id IN (:...ids)", { ids })
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const DefaultProviderMock = {
|
||||
getStatus: jest.fn().mockImplementation(data => {
|
||||
getStatus: jest.fn().mockImplementation((data) => {
|
||||
if (data.money_id === "success") {
|
||||
return Promise.resolve("authorized")
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export const DefaultProviderMock = {
|
||||
|
||||
return Promise.resolve("initial")
|
||||
}),
|
||||
retrievePayment: jest.fn().mockImplementation(data => {
|
||||
retrievePayment: jest.fn().mockImplementation((data) => {
|
||||
return Promise.resolve(data)
|
||||
}),
|
||||
list: jest.fn().mockImplementation(() => {
|
||||
@@ -19,15 +19,26 @@ export const DefaultProviderMock = {
|
||||
capturePayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
refundPayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
cancelPayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
deletePayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
authorizePayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
}
|
||||
|
||||
export const PaymentProviderServiceMock = {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
updateSession: jest.fn().mockImplementation((session, cart) => {
|
||||
return Promise.resolve({
|
||||
...session.data,
|
||||
id: `${session.data.id}_updated`,
|
||||
})
|
||||
}),
|
||||
updateSessionNew: jest.fn().mockImplementation((session, sessionInput) => {
|
||||
return Promise.resolve({
|
||||
...session,
|
||||
id: `${session.id}_updated`,
|
||||
})
|
||||
}),
|
||||
list: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
@@ -40,12 +51,31 @@ export const PaymentProviderServiceMock = {
|
||||
cartId: cart._id,
|
||||
})
|
||||
}),
|
||||
retrieveProvider: jest.fn().mockImplementation(providerId => {
|
||||
createSessionNew: jest.fn().mockImplementation((sessionInput) => {
|
||||
return Promise.resolve({
|
||||
id: `${sessionInput.providerId}_session`,
|
||||
})
|
||||
}),
|
||||
retrieveProvider: jest.fn().mockImplementation((providerId) => {
|
||||
if (providerId === "default_provider") {
|
||||
return DefaultProviderMock
|
||||
}
|
||||
throw new Error("Provider Not Found")
|
||||
}),
|
||||
refreshSessionNew: jest.fn().mockImplementation((session, inputData) => {
|
||||
DefaultProviderMock.deletePayment()
|
||||
PaymentProviderServiceMock.createSessionNew(inputData)
|
||||
return Promise.resolve({
|
||||
...session,
|
||||
id: `${session.id}_refreshed`,
|
||||
})
|
||||
}),
|
||||
authorizePayment: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ status: "authorized" })),
|
||||
createPaymentNew: jest.fn().mockImplementation((session, inputData) => {
|
||||
Promise.resolve(inputData)
|
||||
}),
|
||||
}
|
||||
|
||||
const mock = jest.fn().mockImplementation(() => {
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||
import { EventBusService, PaymentCollectionService } from "../index"
|
||||
import { PaymentCollectionStatus, PaymentCollectionType } from "../../models"
|
||||
import {
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
PaymentCollectionService,
|
||||
PaymentProviderService,
|
||||
} from "../index"
|
||||
import {
|
||||
PaymentCollectionStatus,
|
||||
PaymentCollectionType,
|
||||
PaymentCollection,
|
||||
} from "../../models"
|
||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||
import {
|
||||
DefaultProviderMock,
|
||||
PaymentProviderServiceMock,
|
||||
} from "../__mocks__/payment-provider"
|
||||
import { CustomerServiceMock } from "../__mocks__/customer"
|
||||
import { PaymentCollectionSessionInput } from "../../types/payment-collection"
|
||||
|
||||
describe("PaymentCollectionService", () => {
|
||||
afterEach(() => {
|
||||
@@ -11,6 +26,23 @@ describe("PaymentCollectionService", () => {
|
||||
const paymentCollectionSample = {
|
||||
id: IdMap.getId("payment-collection-id1"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
region: {
|
||||
payment_providers: [
|
||||
{
|
||||
id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
{
|
||||
id: IdMap.getId("region1_provider2"),
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_sessions: [
|
||||
{
|
||||
id: IdMap.getId("payCol_session1"),
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
amount: 100,
|
||||
created_at: new Date(),
|
||||
metadata: {
|
||||
@@ -19,6 +51,28 @@ describe("PaymentCollectionService", () => {
|
||||
status: PaymentCollectionStatus.NOT_PAID,
|
||||
}
|
||||
|
||||
const paymentCollectionWithSessions = {
|
||||
id: IdMap.getId("payment-collection-session"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
region: {
|
||||
payment_providers: [
|
||||
{
|
||||
id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_sessions: [
|
||||
{
|
||||
id: IdMap.getId("payCol_session1"),
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
amount: 100,
|
||||
created_at: new Date(),
|
||||
status: PaymentCollectionStatus.NOT_PAID,
|
||||
} as PaymentCollection
|
||||
|
||||
const paymentCollectionAuthorizedSample = {
|
||||
id: IdMap.getId("payment-collection-id2"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
@@ -26,16 +80,123 @@ describe("PaymentCollectionService", () => {
|
||||
status: PaymentCollectionStatus.AUTHORIZED,
|
||||
}
|
||||
|
||||
const zeroSample = {
|
||||
id: IdMap.getId("payment-collection-zero"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
amount: 0,
|
||||
status: PaymentCollectionStatus.NOT_PAID,
|
||||
}
|
||||
|
||||
const noSessionSample = {
|
||||
id: IdMap.getId("payment-collection-zero"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
amount: 10000,
|
||||
status: PaymentCollectionStatus.NOT_PAID,
|
||||
}
|
||||
|
||||
const fullyAuthorizedSample = {
|
||||
id: IdMap.getId("payment-collection-fully"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
amount: 35000,
|
||||
authorized_amount: 35000,
|
||||
region: {
|
||||
payment_providers: [
|
||||
{
|
||||
id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_sessions: [
|
||||
{
|
||||
id: IdMap.getId("payCol_session1"),
|
||||
payment_authorized_at: Date.now(),
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
amount: 35000,
|
||||
},
|
||||
],
|
||||
payments: [
|
||||
{
|
||||
id: IdMap.getId("payment-123"),
|
||||
amount: 35000,
|
||||
captured_amount: 0,
|
||||
},
|
||||
],
|
||||
status: PaymentCollectionStatus.AUTHORIZED,
|
||||
} as unknown as PaymentCollection
|
||||
|
||||
const partiallyAuthorizedSample = {
|
||||
id: IdMap.getId("payment-collection-partial"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
amount: 70000,
|
||||
authorized_amount: 35000,
|
||||
region: {
|
||||
payment_providers: [
|
||||
{
|
||||
id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_sessions: [
|
||||
{
|
||||
id: IdMap.getId("payCol_session1"),
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
amount: 35000,
|
||||
},
|
||||
{
|
||||
id: IdMap.getId("payCol_session2"),
|
||||
payment_authorized_at: Date.now(),
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
amount: 35000,
|
||||
},
|
||||
],
|
||||
payments: [],
|
||||
status: PaymentCollectionStatus.PARTIALLY_AUTHORIZED,
|
||||
}
|
||||
|
||||
const notAuthorizedSample = {
|
||||
id: IdMap.getId("payment-collection-not-authorized"),
|
||||
region_id: IdMap.getId("region1"),
|
||||
amount: 70000,
|
||||
region: {
|
||||
payment_providers: [
|
||||
{
|
||||
id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_sessions: [
|
||||
{
|
||||
id: IdMap.getId("payCol_session1"),
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
amount: 35000,
|
||||
},
|
||||
{
|
||||
id: IdMap.getId("payCol_session2"),
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
amount: 35000,
|
||||
},
|
||||
],
|
||||
payments: [],
|
||||
status: PaymentCollectionStatus.PARTIALLY_AUTHORIZED,
|
||||
} as unknown as PaymentCollection
|
||||
|
||||
const paymentCollectionRepository = MockRepository({
|
||||
findOne: (query) => {
|
||||
find: (query) => {
|
||||
const map = {
|
||||
[IdMap.getId("payment-collection-id1")]: paymentCollectionSample,
|
||||
[IdMap.getId("payment-collection-id2")]:
|
||||
paymentCollectionAuthorizedSample,
|
||||
[IdMap.getId("payment-collection-session")]:
|
||||
paymentCollectionWithSessions,
|
||||
[IdMap.getId("payment-collection-zero")]: zeroSample,
|
||||
[IdMap.getId("payment-collection-no-session")]: noSessionSample,
|
||||
[IdMap.getId("payment-collection-fully")]: fullyAuthorizedSample,
|
||||
[IdMap.getId("payment-collection-partial")]: partiallyAuthorizedSample,
|
||||
[IdMap.getId("payment-collection-not-authorized")]: notAuthorizedSample,
|
||||
}
|
||||
|
||||
if (map[query?.where?.id]) {
|
||||
return { ...map[query?.where?.id] }
|
||||
return [{ ...map[query?.where?.id] }]
|
||||
}
|
||||
return
|
||||
},
|
||||
@@ -45,20 +206,38 @@ describe("PaymentCollectionService", () => {
|
||||
...data,
|
||||
}
|
||||
},
|
||||
save: (data) => {
|
||||
return data
|
||||
},
|
||||
})
|
||||
|
||||
paymentCollectionRepository.deleteMultiple = jest
|
||||
.fn()
|
||||
.mockImplementation(() => {
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
paymentCollectionRepository.getPaymentCollectionIdBySessionId = jest
|
||||
.fn()
|
||||
.mockImplementation(async () => {
|
||||
return paymentCollectionWithSessions
|
||||
})
|
||||
|
||||
const paymentCollectionService = new PaymentCollectionService({
|
||||
manager: MockManager,
|
||||
paymentCollectionRepository,
|
||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||
paymentProviderService:
|
||||
PaymentProviderServiceMock as unknown as PaymentProviderService,
|
||||
customerService: CustomerServiceMock as unknown as CustomerService,
|
||||
})
|
||||
|
||||
it("should retrieve a payment collection", async () => {
|
||||
await paymentCollectionService.retrieve(
|
||||
IdMap.getId("payment-collection-id1")
|
||||
)
|
||||
expect(paymentCollectionRepository.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(paymentCollectionRepository.findOne).toHaveBeenCalledWith({
|
||||
expect(paymentCollectionRepository.find).toHaveBeenCalledTimes(1)
|
||||
expect(paymentCollectionRepository.find).toHaveBeenCalledWith({
|
||||
where: { id: IdMap.getId("payment-collection-id1") },
|
||||
})
|
||||
})
|
||||
@@ -68,8 +247,8 @@ describe("PaymentCollectionService", () => {
|
||||
IdMap.getId("payment-collection-non-existing-id")
|
||||
)
|
||||
|
||||
expect(paymentCollectionRepository.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(payCol).rejects.toThrow(Error)
|
||||
expect(paymentCollectionRepository.find).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should create a payment collection", async () => {
|
||||
@@ -155,9 +334,10 @@ describe("PaymentCollectionService", () => {
|
||||
IdMap.getId("payment-collection-non-existing"),
|
||||
submittedChanges
|
||||
)
|
||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(0)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||
|
||||
expect(payCol).rejects.toThrow(Error)
|
||||
expect(paymentCollectionRepository.save).toBeCalledTimes(0)
|
||||
expect(EventBusServiceMock.emit).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("should delete a payment collection", async () => {
|
||||
@@ -197,4 +377,312 @@ describe("PaymentCollectionService", () => {
|
||||
|
||||
expect(entity).rejects.toThrow(Error)
|
||||
})
|
||||
|
||||
describe("Manage Payment Sessions", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should throw error if payment collection doesn't have the correct status", async () => {
|
||||
const inp: PaymentCollectionSessionInput = {
|
||||
amount: 100,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
}
|
||||
const ret = paymentCollectionService.setPaymentSessions(
|
||||
IdMap.getId("payment-collection-id2"),
|
||||
inp
|
||||
)
|
||||
|
||||
expect(ret).rejects.toThrowError(
|
||||
new Error(
|
||||
`Cannot set payment sessions for a payment collection with status ${PaymentCollectionStatus.AUTHORIZED}`
|
||||
)
|
||||
)
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("should throw error if amount is different than requested", async () => {
|
||||
const inp: PaymentCollectionSessionInput = {
|
||||
amount: 101,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
}
|
||||
const ret = paymentCollectionService.setPaymentSessions(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
inp
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(ret).rejects.toThrow(
|
||||
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||
)
|
||||
|
||||
const multInp: PaymentCollectionSessionInput[] = [
|
||||
{
|
||||
amount: 51,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider2"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
]
|
||||
const multiRet = paymentCollectionService.setPaymentSessions(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
multInp
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(multiRet).rejects.toThrow(
|
||||
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||
)
|
||||
})
|
||||
|
||||
it("should ignore sessions where provider doesn't belong to the region", async () => {
|
||||
const multInp: PaymentCollectionSessionInput[] = [
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_invalid_provider"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
]
|
||||
const multiRet = paymentCollectionService.setPaymentSessions(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
multInp
|
||||
)
|
||||
|
||||
expect(multiRet).rejects.toThrow(
|
||||
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||
)
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("should add a new session and update existing one", async () => {
|
||||
const inp: PaymentCollectionSessionInput[] = [
|
||||
{
|
||||
session_id: IdMap.getId("payCol_session1"),
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
]
|
||||
await paymentCollectionService.setPaymentSessions(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
inp
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should add a new session and delete existing one", async () => {
|
||||
const inp: PaymentCollectionSessionInput[] = [
|
||||
{
|
||||
amount: 100,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
]
|
||||
await paymentCollectionService.setPaymentSessions(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
inp
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(paymentCollectionRepository.deleteMultiple).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
|
||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should refresh a payment session", async () => {
|
||||
await paymentCollectionService.refreshPaymentSession(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
IdMap.getId("payCol_session1"),
|
||||
{
|
||||
customer_id: "customer1",
|
||||
amount: 100,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
}
|
||||
)
|
||||
|
||||
expect(
|
||||
PaymentProviderServiceMock.refreshSessionNew
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(DefaultProviderMock.deletePayment).toHaveBeenCalledTimes(1)
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to refresh a payment session if the amount is different", async () => {
|
||||
const sess = paymentCollectionService.refreshPaymentSession(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
IdMap.getId("payCol_session1"),
|
||||
{
|
||||
customer_id: "customer1",
|
||||
amount: 80,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
}
|
||||
)
|
||||
|
||||
expect(sess).rejects.toThrow(
|
||||
"The amount has to be the same as the existing payment session"
|
||||
)
|
||||
expect(PaymentProviderServiceMock.refreshSessionNew).toBeCalledTimes(0)
|
||||
expect(DefaultProviderMock.deletePayment).toBeCalledTimes(0)
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Authorize Payments", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should mark as paid if amount is 0", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-zero")
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
it("should reject payment collection without payment sessions", async () => {
|
||||
const ret = paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-no-session")
|
||||
)
|
||||
|
||||
expect(ret).rejects.toThrowError(
|
||||
new Error(
|
||||
"You cannot complete a Payment Collection without a payment session."
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
it("should call authorizePayments for all sessions", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-not-authorized")
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
2
|
||||
)
|
||||
expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes(
|
||||
2
|
||||
)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should skip authorized sessions - partially authorized", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-partial")
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should skip authorized sessions - fully authorized", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-fully")
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Capture Payments", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should throw error if the status is not authorized", async () => {
|
||||
paymentCollectionRepository.getPaymentCollectionIdByPaymentId = jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve(notAuthorizedSample))
|
||||
|
||||
PaymentProviderServiceMock.capturePayment = jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve())
|
||||
|
||||
const ret = paymentCollectionService.capture(
|
||||
IdMap.getId("payment-collection-not-authorized")
|
||||
)
|
||||
|
||||
expect(ret).rejects.toThrowError(
|
||||
new Error(
|
||||
`A Payment Collection with status ${PaymentCollectionStatus.PARTIALLY_AUTHORIZED} cannot capture payment`
|
||||
)
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.capturePayment).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("should emit PAYMENT_CAPTURE_FAILED if payment capture has failed", async () => {
|
||||
paymentCollectionRepository.getPaymentCollectionIdByPaymentId = jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve(fullyAuthorizedSample))
|
||||
|
||||
PaymentProviderServiceMock.retrievePayment = jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
id: IdMap.getId("payment-123"),
|
||||
amount: 35000,
|
||||
captured_amount: 0,
|
||||
})
|
||||
)
|
||||
|
||||
PaymentProviderServiceMock.capturePayment = jest
|
||||
.fn()
|
||||
.mockRejectedValue("capture failed")
|
||||
|
||||
const ret = paymentCollectionService.capture(IdMap.getId("payment-123"))
|
||||
|
||||
expect(ret).rejects.toThrowError(
|
||||
new Error(`Failed to capture Payment ${IdMap.getId("payment-123")}`)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
import { DeepPartial, EntityManager, IsNull } from "typeorm"
|
||||
import { DeepPartial, EntityManager, Equal } from "typeorm"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
import { FindConfig } from "../types/common"
|
||||
import { buildQuery, isDefined, setMetadata } from "../utils"
|
||||
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
||||
import { PaymentCollection, PaymentCollectionStatus } from "../models"
|
||||
import {
|
||||
Customer,
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
PaymentCollectionStatus,
|
||||
PaymentSession,
|
||||
PaymentSessionStatus,
|
||||
Refund,
|
||||
} from "../models"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { EventBusService } from "./index"
|
||||
import {
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
PaymentProviderService,
|
||||
} from "./index"
|
||||
|
||||
import { CreatePaymentCollectionInput } from "../types/payment-collection"
|
||||
import {
|
||||
CreatePaymentCollectionInput,
|
||||
PaymentCollectionSessionInput,
|
||||
PaymentProviderDataInput,
|
||||
} from "../types/payment-collection"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
paymentCollectionRepository: typeof PaymentCollectionRepository
|
||||
paymentProviderService: PaymentProviderService
|
||||
eventBusService: EventBusService
|
||||
customerService: CustomerService
|
||||
}
|
||||
|
||||
export default class PaymentCollectionService extends TransactionBaseService {
|
||||
@@ -21,17 +39,26 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
CREATED: "payment-collection.created",
|
||||
UPDATED: "payment-collection.updated",
|
||||
DELETED: "payment-collection.deleted",
|
||||
PAYMENT_AUTHORIZED: "payment-collection.payment_authorized",
|
||||
PAYMENT_CAPTURED: "payment-collection.payment_captured",
|
||||
PAYMENT_CAPTURE_FAILED: "payment-collection.payment_capture_failed",
|
||||
REFUND_CREATED: "payment-collection.payment_refund_created",
|
||||
REFUND_FAILED: "payment-collection.payment_refund_failed",
|
||||
}
|
||||
|
||||
protected readonly manager_: EntityManager
|
||||
protected transactionManager_: EntityManager | undefined
|
||||
protected readonly eventBusService_: EventBusService
|
||||
protected readonly paymentProviderService_: PaymentProviderService
|
||||
protected readonly customerService_: CustomerService
|
||||
// eslint-disable-next-line max-len
|
||||
protected readonly paymentCollectionRepository_: typeof PaymentCollectionRepository
|
||||
|
||||
constructor({
|
||||
manager,
|
||||
paymentCollectionRepository,
|
||||
paymentProviderService,
|
||||
customerService,
|
||||
eventBusService,
|
||||
}: InjectedDependencies) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
@@ -39,7 +66,9 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
|
||||
this.manager_ = manager
|
||||
this.paymentCollectionRepository_ = paymentCollectionRepository
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
this.eventBusService_ = eventBusService
|
||||
this.customerService_ = customerService
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
@@ -52,24 +81,24 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
)
|
||||
|
||||
const query = buildQuery({ id: paymentCollectionId }, config)
|
||||
const paymentCollection = await paymentCollectionRepository.findOne(query)
|
||||
|
||||
if (!paymentCollection) {
|
||||
const paymentCollection = await paymentCollectionRepository.find(query)
|
||||
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Payment collection with id ${paymentCollectionId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return paymentCollection
|
||||
return paymentCollection[0]
|
||||
}
|
||||
|
||||
async create(data: CreatePaymentCollectionInput): Promise<PaymentCollection> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const paymentCollectionRepository =
|
||||
transactionManager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const paymentCollectionToCreate = paymentCollectionRepository.create({
|
||||
region_id: data.region_id,
|
||||
@@ -87,7 +116,7 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(transactionManager)
|
||||
.withTransaction(manager)
|
||||
.emit(PaymentCollectionService.Events.CREATED, paymentCollection)
|
||||
|
||||
return paymentCollection
|
||||
@@ -160,4 +189,493 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
return paymentCollection
|
||||
})
|
||||
}
|
||||
|
||||
private isValidTotalAmount(
|
||||
total: number,
|
||||
sessionsInput: PaymentCollectionSessionInput[]
|
||||
): boolean {
|
||||
const sum = sessionsInput.reduce((cur, sess) => cur + sess.amount, 0)
|
||||
return total === sum
|
||||
}
|
||||
|
||||
async setPaymentSessions(
|
||||
paymentCollectionId: string,
|
||||
sessions: PaymentCollectionSessionInput[] | PaymentCollectionSessionInput
|
||||
): Promise<PaymentCollection> {
|
||||
let sessionsInput = Array.isArray(sessions) ? sessions : [sessions]
|
||||
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const payCol = await this.retrieve(paymentCollectionId, {
|
||||
relations: ["region", "region.payment_providers", "payment_sessions"],
|
||||
})
|
||||
|
||||
if (payCol.status !== PaymentCollectionStatus.NOT_PAID) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot set payment sessions for a payment collection with status ${payCol.status}`
|
||||
)
|
||||
}
|
||||
|
||||
sessionsInput = sessionsInput.filter((session) => {
|
||||
return !!payCol.region.payment_providers.find(({ id }) => {
|
||||
return id === session.provider_id
|
||||
})
|
||||
})
|
||||
|
||||
if (!this.isValidTotalAmount(payCol.amount, sessionsInput)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNEXPECTED_STATE,
|
||||
`The sum of sessions is not equal to ${payCol.amount} on Payment Collection`
|
||||
)
|
||||
}
|
||||
|
||||
let customer: Customer | undefined = undefined
|
||||
|
||||
const selectedSessionIds: string[] = []
|
||||
const paymentSessions: PaymentSession[] = []
|
||||
|
||||
for (const session of sessionsInput) {
|
||||
if (!customer) {
|
||||
customer = await this.customerService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(session.customer_id, {
|
||||
select: ["id", "email", "metadata"],
|
||||
})
|
||||
}
|
||||
|
||||
const existingSession = payCol.payment_sessions?.find(
|
||||
(sess) => session.session_id === sess?.id
|
||||
)
|
||||
|
||||
const inputData: PaymentProviderDataInput = {
|
||||
resource_id: payCol.id,
|
||||
currency_code: payCol.currency_code,
|
||||
amount: session.amount,
|
||||
provider_id: session.provider_id,
|
||||
customer,
|
||||
metadata: {
|
||||
resource_id: payCol.id,
|
||||
},
|
||||
}
|
||||
|
||||
if (existingSession) {
|
||||
const paymentSession = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.updateSessionNew(existingSession, inputData)
|
||||
|
||||
selectedSessionIds.push(existingSession.id)
|
||||
paymentSessions.push(paymentSession)
|
||||
} else {
|
||||
const paymentSession = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.createSessionNew(inputData)
|
||||
|
||||
selectedSessionIds.push(paymentSession.id)
|
||||
paymentSessions.push(paymentSession)
|
||||
}
|
||||
}
|
||||
|
||||
if (payCol.payment_sessions?.length) {
|
||||
const removeIds: string[] = payCol.payment_sessions
|
||||
.map((sess) => sess.id)
|
||||
.filter((id) => !selectedSessionIds.includes(id))
|
||||
|
||||
if (removeIds.length) {
|
||||
await paymentCollectionRepository.deleteMultiple(removeIds)
|
||||
}
|
||||
}
|
||||
|
||||
payCol.payment_sessions = paymentSessions
|
||||
|
||||
return await paymentCollectionRepository.save(payCol)
|
||||
})
|
||||
}
|
||||
|
||||
async refreshPaymentSession(
|
||||
paymentCollectionId: string,
|
||||
sessionId: string,
|
||||
sessionInput: PaymentCollectionSessionInput
|
||||
): Promise<PaymentSession> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const payCol =
|
||||
await paymentCollectionRepository.getPaymentCollectionIdBySessionId(
|
||||
sessionId,
|
||||
{
|
||||
relations: [
|
||||
"region",
|
||||
"region.payment_providers",
|
||||
"payment_sessions",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
if (paymentCollectionId !== payCol.id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Payment Session ${sessionId} does not belong to Payment Collection ${paymentCollectionId}`
|
||||
)
|
||||
}
|
||||
|
||||
const session = payCol.payment_sessions.find(
|
||||
(sess) => sessionId === sess?.id
|
||||
)
|
||||
|
||||
if (session?.amount !== sessionInput.amount) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"The amount has to be the same as the existing payment session"
|
||||
)
|
||||
}
|
||||
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(sessionInput.customer_id, {
|
||||
select: ["id", "email", "metadata"],
|
||||
})
|
||||
|
||||
const inputData: PaymentProviderDataInput = {
|
||||
resource_id: payCol.id,
|
||||
currency_code: payCol.currency_code,
|
||||
amount: session.amount,
|
||||
provider_id: session.provider_id,
|
||||
customer,
|
||||
}
|
||||
|
||||
const sessionRefreshed = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.refreshSessionNew(session, inputData)
|
||||
|
||||
payCol.payment_sessions = payCol.payment_sessions.map((sess) => {
|
||||
if (sess.id === sessionId) {
|
||||
return sessionRefreshed
|
||||
}
|
||||
return sess
|
||||
})
|
||||
|
||||
if (session.payment_authorized_at) {
|
||||
payCol.authorized_amount -= session.amount
|
||||
}
|
||||
|
||||
await paymentCollectionRepository.save(payCol)
|
||||
|
||||
return sessionRefreshed
|
||||
})
|
||||
}
|
||||
|
||||
async authorize(
|
||||
paymentCollectionId: string,
|
||||
context: Record<string, unknown> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const payCol = await this.retrieve(paymentCollectionId, {
|
||||
relations: ["payment_sessions", "payments"],
|
||||
})
|
||||
|
||||
if (payCol.authorized_amount === payCol.amount) {
|
||||
return payCol
|
||||
}
|
||||
|
||||
// If cart total is 0, we don't perform anything payment related
|
||||
if (payCol.amount <= 0) {
|
||||
payCol.authorized_amount = 0
|
||||
return await paymentCollectionRepository.save(payCol)
|
||||
}
|
||||
|
||||
if (!payCol.payment_sessions?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"You cannot complete a Payment Collection without a payment session."
|
||||
)
|
||||
}
|
||||
|
||||
let authorizedAmount = 0
|
||||
for (const session of payCol.payment_sessions) {
|
||||
if (session.payment_authorized_at) {
|
||||
authorizedAmount += session.amount
|
||||
continue
|
||||
}
|
||||
|
||||
const auth = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.authorizePayment(session, context)
|
||||
|
||||
if (auth?.status === PaymentSessionStatus.AUTHORIZED) {
|
||||
authorizedAmount += session.amount
|
||||
|
||||
const inputData: Omit<PaymentProviderDataInput, "customer"> = {
|
||||
amount: session.amount,
|
||||
currency_code: payCol.currency_code,
|
||||
provider_id: session.provider_id,
|
||||
resource_id: payCol.id,
|
||||
}
|
||||
|
||||
payCol.payments.push(
|
||||
await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.createPaymentNew(inputData)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (authorizedAmount === 0) {
|
||||
payCol.status = PaymentCollectionStatus.AWAITING
|
||||
} else if (authorizedAmount < payCol.amount) {
|
||||
payCol.status = PaymentCollectionStatus.PARTIALLY_AUTHORIZED
|
||||
} else if (authorizedAmount === payCol.amount) {
|
||||
payCol.status = PaymentCollectionStatus.AUTHORIZED
|
||||
}
|
||||
|
||||
payCol.authorized_amount = authorizedAmount
|
||||
const payColCopy = await paymentCollectionRepository.save(payCol)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(PaymentCollectionService.Events.PAYMENT_AUTHORIZED, payColCopy)
|
||||
|
||||
return payCol
|
||||
})
|
||||
}
|
||||
|
||||
private async capturePayment(
|
||||
payCol: PaymentCollection,
|
||||
payment: Payment
|
||||
): Promise<Payment> {
|
||||
if (payment?.captured_at) {
|
||||
return payment
|
||||
}
|
||||
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const allowedStatuses = [
|
||||
PaymentCollectionStatus.AUTHORIZED,
|
||||
PaymentCollectionStatus.PARTIALLY_CAPTURED,
|
||||
PaymentCollectionStatus.REQUIRES_ACTION,
|
||||
]
|
||||
|
||||
if (!allowedStatuses.includes(payCol.status)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`A Payment Collection with status ${payCol.status} cannot capture payment`
|
||||
)
|
||||
}
|
||||
|
||||
let captureError: Error | null = null
|
||||
const capturedPayment = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.capturePayment(payment)
|
||||
.catch((err) => {
|
||||
captureError = err
|
||||
})
|
||||
|
||||
payCol.captured_amount = payCol.captured_amount ?? 0
|
||||
if (capturedPayment) {
|
||||
payCol.captured_amount += payment.amount
|
||||
}
|
||||
|
||||
if (payCol.captured_amount === 0) {
|
||||
payCol.status = PaymentCollectionStatus.REQUIRES_ACTION
|
||||
} else if (payCol.captured_amount === payCol.amount) {
|
||||
payCol.status = PaymentCollectionStatus.CAPTURED
|
||||
} else {
|
||||
payCol.status = PaymentCollectionStatus.PARTIALLY_CAPTURED
|
||||
}
|
||||
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
await paymentCollectionRepository.save(payCol)
|
||||
|
||||
if (!capturedPayment) {
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(PaymentCollectionService.Events.PAYMENT_CAPTURE_FAILED, {
|
||||
...payment,
|
||||
error: captureError,
|
||||
})
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNEXPECTED_STATE,
|
||||
`Failed to capture Payment ${payment.id}`
|
||||
)
|
||||
}
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(PaymentCollectionService.Events.PAYMENT_CAPTURED, capturedPayment)
|
||||
|
||||
return capturedPayment
|
||||
})
|
||||
}
|
||||
|
||||
async capture(paymentId: string): Promise<Payment> {
|
||||
const manager = this.transactionManager_ ?? this.manager_
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const payCol =
|
||||
await paymentCollectionRepository.getPaymentCollectionIdByPaymentId(
|
||||
paymentId,
|
||||
{
|
||||
relations: ["payments"],
|
||||
}
|
||||
)
|
||||
|
||||
const payment = payCol.payments.find((payment) => paymentId === payment?.id)
|
||||
|
||||
return await this.capturePayment(payCol, payment!)
|
||||
}
|
||||
|
||||
async captureAll(paymentCollectionId: string): Promise<Payment[]> {
|
||||
const payCol = await this.retrieve(paymentCollectionId, {
|
||||
relations: ["payments"],
|
||||
})
|
||||
|
||||
const allPayments: Payment[] = []
|
||||
for (const payment of payCol.payments) {
|
||||
const captured = await this.capturePayment(payCol, payment).catch(
|
||||
() => void 0
|
||||
)
|
||||
|
||||
if (captured) {
|
||||
allPayments.push(captured)
|
||||
}
|
||||
}
|
||||
|
||||
return allPayments
|
||||
}
|
||||
|
||||
private async refundPayment(
|
||||
payCol: PaymentCollection,
|
||||
payment: Payment,
|
||||
amount: number,
|
||||
reason: string,
|
||||
note?: string
|
||||
): Promise<Refund> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
if (!payment.captured_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Payment ${payment.id} is not captured`
|
||||
)
|
||||
}
|
||||
|
||||
const refundable = payment.amount - payment.amount_refunded
|
||||
if (amount > refundable) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Only ${refundable} can be refunded from Payment ${payment.id}`
|
||||
)
|
||||
}
|
||||
|
||||
let refundError: Error | null = null
|
||||
const refund = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.refundFromPayment(payment, amount, reason, note)
|
||||
.catch((err) => {
|
||||
refundError = err
|
||||
})
|
||||
|
||||
payCol.refunded_amount = payCol.refunded_amount ?? 0
|
||||
if (refund) {
|
||||
payCol.refunded_amount += refund.amount
|
||||
}
|
||||
|
||||
if (payCol.refunded_amount === 0) {
|
||||
payCol.status = PaymentCollectionStatus.REQUIRES_ACTION
|
||||
} else if (payCol.refunded_amount === payCol.amount) {
|
||||
payCol.status = PaymentCollectionStatus.REFUNDED
|
||||
} else {
|
||||
payCol.status = PaymentCollectionStatus.PARTIALLY_REFUNDED
|
||||
}
|
||||
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
await paymentCollectionRepository.save(payCol)
|
||||
|
||||
if (!refund) {
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(PaymentCollectionService.Events.REFUND_FAILED, {
|
||||
...payment,
|
||||
error: refundError,
|
||||
})
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNEXPECTED_STATE,
|
||||
`Failed to refund Payment ${payment.id}`
|
||||
)
|
||||
}
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(PaymentCollectionService.Events.REFUND_CREATED, refund)
|
||||
|
||||
return refund
|
||||
})
|
||||
}
|
||||
|
||||
async refund(
|
||||
paymentId: string,
|
||||
amount: number,
|
||||
reason: string,
|
||||
note?: string
|
||||
): Promise<Refund> {
|
||||
const manager = this.transactionManager_ ?? this.manager_
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const payCol =
|
||||
await paymentCollectionRepository.getPaymentCollectionIdByPaymentId(
|
||||
paymentId
|
||||
)
|
||||
|
||||
const payment = await this.paymentProviderService_.retrievePayment(
|
||||
paymentId
|
||||
)
|
||||
|
||||
return await this.refundPayment(payCol, payment, amount, reason, note)
|
||||
}
|
||||
|
||||
async refundAll(
|
||||
paymentCollectionId: string,
|
||||
reason: string,
|
||||
note?: string
|
||||
): Promise<Refund[]> {
|
||||
const payCol = await this.retrieve(paymentCollectionId, {
|
||||
relations: ["payments"],
|
||||
})
|
||||
|
||||
const allRefunds: Refund[] = []
|
||||
for (const payment of payCol.payments) {
|
||||
const refunded = await this.refundPayment(
|
||||
payCol,
|
||||
payment,
|
||||
payment.amount,
|
||||
reason,
|
||||
note
|
||||
).catch(() => void 0)
|
||||
|
||||
if (refunded) {
|
||||
allRefunds.push(refunded)
|
||||
}
|
||||
}
|
||||
|
||||
return allRefunds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
PaymentSessionStatus,
|
||||
Refund,
|
||||
} from "../models"
|
||||
import { PaymentProviderDataInput } from "../types/payment-collection"
|
||||
|
||||
type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService"
|
||||
type InjectedDependencies = {
|
||||
@@ -179,6 +180,33 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async createSessionNew(
|
||||
sessionInput: PaymentProviderDataInput
|
||||
): Promise<PaymentSession> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const provider: AbstractPaymentService = this.retrieveProvider(
|
||||
sessionInput.provider_id
|
||||
)
|
||||
const sessionData = await provider
|
||||
.withTransaction(transactionManager)
|
||||
.createPaymentNew(sessionInput)
|
||||
|
||||
const sessionRepo = transactionManager.getCustomRepository(
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
|
||||
const toCreate = {
|
||||
provider_id: sessionInput.provider_id,
|
||||
data: sessionData,
|
||||
status: "pending",
|
||||
amount: sessionInput.amount,
|
||||
} as PaymentSession
|
||||
|
||||
const created = sessionRepo.create(toCreate)
|
||||
return await sessionRepo.save(created)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes a payment session with the given provider.
|
||||
* This means, that we delete the current one and create a new.
|
||||
@@ -219,6 +247,26 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async refreshSessionNew(
|
||||
paymentSession: PaymentSession,
|
||||
sessionInput: PaymentProviderDataInput
|
||||
): Promise<PaymentSession> {
|
||||
return this.atomicPhase_(async (transactionManager) => {
|
||||
const session = await this.retrieveSession(paymentSession.id)
|
||||
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||
|
||||
await provider.withTransaction(transactionManager).deletePayment(session)
|
||||
|
||||
const sessionRepo = transactionManager.getCustomRepository(
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
|
||||
await sessionRepo.remove(session)
|
||||
|
||||
return await this.createSessionNew(sessionInput)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing payment session.
|
||||
* @param paymentSession - the payment session object to
|
||||
@@ -240,7 +288,29 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
const sessionRepo = transactionManager.getCustomRepository(
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
return sessionRepo.save(session)
|
||||
return await sessionRepo.save(session)
|
||||
})
|
||||
}
|
||||
|
||||
async updateSessionNew(
|
||||
paymentSession: PaymentSession,
|
||||
sessionInput: PaymentProviderDataInput
|
||||
): Promise<PaymentSession> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const session = await this.retrieveSession(paymentSession.id)
|
||||
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||
|
||||
session.amount = sessionInput.amount
|
||||
paymentSession.data.amount = sessionInput.amount
|
||||
session.data = await provider
|
||||
.withTransaction(transactionManager)
|
||||
.updatePaymentNew(paymentSession.data, sessionInput)
|
||||
|
||||
const sessionRepo = transactionManager.getCustomRepository(
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
|
||||
return await sessionRepo.save(session)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -265,7 +335,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
|
||||
return sessionRepo.remove(session)
|
||||
return await sessionRepo.remove(session)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -321,7 +391,34 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
cart_id: cart.id,
|
||||
})
|
||||
|
||||
return paymentRepo.save(created)
|
||||
return await paymentRepo.save(created)
|
||||
})
|
||||
}
|
||||
|
||||
async createPaymentNew(
|
||||
paymentInput: Omit<PaymentProviderDataInput, "customer">
|
||||
): Promise<Payment> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const { payment_session, currency_code, amount, provider_id } =
|
||||
paymentInput
|
||||
|
||||
const provider = this.retrieveProvider(provider_id)
|
||||
const paymentData = await provider
|
||||
.withTransaction(transactionManager)
|
||||
.getPaymentData(payment_session)
|
||||
|
||||
const paymentRepo = transactionManager.getCustomRepository(
|
||||
this.paymentRepository_
|
||||
)
|
||||
|
||||
const created = paymentRepo.create({
|
||||
provider_id,
|
||||
amount,
|
||||
currency_code,
|
||||
data: paymentData,
|
||||
})
|
||||
|
||||
return await paymentRepo.save(created)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -343,7 +440,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
const payRepo = transactionManager.getCustomRepository(
|
||||
this.paymentRepository_
|
||||
)
|
||||
return payRepo.save(payment)
|
||||
return await payRepo.save(payment)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -371,7 +468,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
const sessionRepo = transactionManager.getCustomRepository(
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
return sessionRepo.save(session)
|
||||
return await sessionRepo.save(session)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -392,7 +489,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
const sessionRepo = transactionManager.getCustomRepository(
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
return sessionRepo.save(session)
|
||||
return await sessionRepo.save(session)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -437,7 +534,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
const paymentRepo = transactionManager.getCustomRepository(
|
||||
this.paymentRepository_
|
||||
)
|
||||
return paymentRepo.save(payment)
|
||||
return await paymentRepo.save(payment)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -521,7 +618,47 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
const created = refundRepo.create(toCreate)
|
||||
return refundRepo.save(created)
|
||||
return await refundRepo.save(created)
|
||||
})
|
||||
}
|
||||
|
||||
async refundFromPayment(
|
||||
payment: Payment,
|
||||
amount: number,
|
||||
reason: string,
|
||||
note?: string
|
||||
): Promise<Refund> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const refundable = payment.amount - payment.amount_refunded
|
||||
|
||||
if (refundable < amount) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Refund amount is higher that the refundable amount"
|
||||
)
|
||||
}
|
||||
|
||||
const provider = this.retrieveProvider(payment.provider_id)
|
||||
payment.data = await provider
|
||||
.withTransaction(manager)
|
||||
.refundPayment(payment, amount)
|
||||
|
||||
payment.amount_refunded += amount
|
||||
|
||||
const paymentRepo = manager.getCustomRepository(this.paymentRepository_)
|
||||
await paymentRepo.save(payment)
|
||||
|
||||
const refundRepo = manager.getCustomRepository(this.refundRepository_)
|
||||
|
||||
const toCreate = {
|
||||
payment_id: payment.id,
|
||||
amount,
|
||||
reason,
|
||||
note,
|
||||
}
|
||||
|
||||
const created = refundRepo.create(toCreate)
|
||||
return await refundRepo.save(created)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { PaymentCollection, PaymentCollectionType } from "../models"
|
||||
import {
|
||||
Cart,
|
||||
Customer,
|
||||
PaymentCollection,
|
||||
PaymentCollectionType,
|
||||
PaymentSession,
|
||||
} from "../models"
|
||||
|
||||
export type CreatePaymentCollectionInput = {
|
||||
region_id: string
|
||||
@@ -10,6 +16,25 @@ export type CreatePaymentCollectionInput = {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type PaymentCollectionSessionInput = {
|
||||
provider_id: string
|
||||
amount: number
|
||||
session_id?: string
|
||||
customer_id: string
|
||||
}
|
||||
|
||||
export type PaymentProviderDataInput = {
|
||||
resource_id: string
|
||||
customer: Partial<Customer>
|
||||
currency_code: string
|
||||
provider_id: string
|
||||
amount: number
|
||||
payment_session?: PaymentSession
|
||||
payment_description?: string
|
||||
cart_id?: string
|
||||
cart?: Cart
|
||||
metadata?: any
|
||||
}
|
||||
export const defaultPaymentCollectionRelations = [
|
||||
"region",
|
||||
"region.payment_providers",
|
||||
|
||||
Reference in New Issue
Block a user