UNPKG

@medusajs/payment-stripe

Version:

Stripe payment provider for Medusa

388 lines • 16.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const stripe_1 = __importDefault(require("stripe")); const utils_1 = require("@medusajs/framework/utils"); const types_1 = require("../types"); const get_smallest_unit_1 = require("../utils/get-smallest-unit"); class StripeBase extends utils_1.AbstractPaymentProvider { static validateOptions(options) { if (!(0, utils_1.isDefined)(options.apiKey)) { throw new Error("Required option `apiKey` is missing in Stripe plugin"); } } constructor(cradle, options) { // @ts-ignore super(...arguments); this.container_ = cradle; this.options_ = options; this.stripe_ = new stripe_1.default(options.apiKey); } get options() { return this.options_; } normalizePaymentIntentParameters(extra) { const res = {}; res.description = (extra?.payment_description ?? this.options_?.paymentDescription); res.capture_method = extra?.capture_method ?? this.paymentIntentOptions.capture_method ?? (this.options_.capture ? "automatic" : "manual"); res.setup_future_usage = extra?.setup_future_usage ?? this.paymentIntentOptions.setup_future_usage; res.payment_method_types = this.paymentIntentOptions .payment_method_types; res.automatic_payment_methods = extra?.automatic_payment_methods ?? (this.options_?.automaticPaymentMethods ? { enabled: true } : undefined); res.off_session = extra?.off_session; res.confirm = extra?.confirm; res.payment_method = extra?.payment_method; res.return_url = extra?.return_url; return res; } async getPaymentStatus({ data, }) { const id = data?.id; if (!id) { throw this.buildError("No payment intent ID provided while getting payment status", new Error("No payment intent ID provided")); } const paymentIntent = await this.stripe_.paymentIntents.retrieve(id); const dataResponse = paymentIntent; switch (paymentIntent.status) { case "requires_payment_method": if (paymentIntent.last_payment_error) { return { status: utils_1.PaymentSessionStatus.ERROR, data: dataResponse }; } return { status: utils_1.PaymentSessionStatus.PENDING, data: dataResponse }; case "requires_confirmation": case "processing": return { status: utils_1.PaymentSessionStatus.PENDING, data: dataResponse }; case "requires_action": return { status: utils_1.PaymentSessionStatus.REQUIRES_MORE, data: dataResponse, }; case "canceled": return { status: utils_1.PaymentSessionStatus.CANCELED, data: dataResponse }; case "requires_capture": return { status: utils_1.PaymentSessionStatus.AUTHORIZED, data: dataResponse }; case "succeeded": return { status: utils_1.PaymentSessionStatus.CAPTURED, data: dataResponse }; default: return { status: utils_1.PaymentSessionStatus.PENDING, data: dataResponse }; } } async initiatePayment({ currency_code, amount, data, context, }) { const additionalParameters = this.normalizePaymentIntentParameters(data); const intentRequest = { amount: (0, get_smallest_unit_1.getSmallestUnit)(amount, currency_code), currency: currency_code, metadata: { session_id: data?.session_id }, ...additionalParameters, }; intentRequest.customer = context?.account_holder?.data?.id; let sessionData; try { sessionData = (await this.stripe_.paymentIntents.create(intentRequest, { idempotencyKey: context?.idempotency_key, })); } catch (e) { throw this.buildError("An error occurred in InitiatePayment during the creation of the stripe payment intent", e); } return { id: sessionData.id, data: sessionData, }; } async authorizePayment(input) { const statusResponse = await this.getPaymentStatus(input); return statusResponse; } async cancelPayment({ data, context, }) { try { const id = data?.id; if (!id) { return { data: data }; } const res = await this.stripe_.paymentIntents.cancel(id, { idempotencyKey: context?.idempotency_key, }); return { data: res }; } catch (error) { if (error.payment_intent?.status === types_1.ErrorIntentStatus.CANCELED) { return { data: error.payment_intent }; } throw this.buildError("An error occurred in cancelPayment", error); } } async capturePayment({ data, context, }) { const id = data?.id; try { const intent = await this.stripe_.paymentIntents.capture(id, { idempotencyKey: context?.idempotency_key, }); return { data: intent }; } catch (error) { if (error.code === types_1.ErrorCodes.PAYMENT_INTENT_UNEXPECTED_STATE) { if (error.payment_intent?.status === types_1.ErrorIntentStatus.SUCCEEDED) { return { data: error.payment_intent }; } } throw this.buildError("An error occurred in capturePayment", error); } } async deletePayment(input) { return await this.cancelPayment(input); } async refundPayment({ amount, data, context, }) { const id = data?.id; if (!id) { throw this.buildError("No payment intent ID provided while refunding payment", new Error("No payment intent ID provided")); } try { const currencyCode = data?.currency; await this.stripe_.refunds.create({ amount: (0, get_smallest_unit_1.getSmallestUnit)(amount, currencyCode), payment_intent: id, }, { idempotencyKey: context?.idempotency_key, }); } catch (e) { throw this.buildError("An error occurred in refundPayment", e); } return { data }; } async retrievePayment({ data, }) { try { const id = data?.id; const intent = await this.stripe_.paymentIntents.retrieve(id); intent.amount = (0, get_smallest_unit_1.getAmountFromSmallestUnit)(intent.amount, intent.currency); return { data: intent }; } catch (e) { throw this.buildError("An error occurred in retrievePayment", e); } } async updatePayment({ data, currency_code, amount, context, }) { const amountNumeric = (0, get_smallest_unit_1.getSmallestUnit)(amount, currency_code); if ((0, utils_1.isPresent)(amount) && data?.amount === amountNumeric) { return { data }; } try { const id = data?.id; const sessionData = (await this.stripe_.paymentIntents.update(id, { amount: amountNumeric, }, { idempotencyKey: context?.idempotency_key, })); return { data: sessionData }; } catch (e) { throw this.buildError("An error occurred in updatePayment", e); } } async createAccountHolder({ context, }) { const { account_holder, customer, idempotency_key } = context; if (account_holder?.data?.id) { return { id: account_holder.data.id }; } if (!customer) { throw this.buildError("No customer in context", new Error("No customer provided while creating account holder")); } const shipping = customer.billing_address ? { address: { city: customer.billing_address.city, country: customer.billing_address.country_code, line1: customer.billing_address.address_1, line2: customer.billing_address.address_2, postal_code: customer.billing_address.postal_code, state: customer.billing_address.province, }, } : undefined; try { const stripeCustomer = await this.stripe_.customers.create({ email: customer.email, name: customer.company_name || `${customer.first_name ?? ""} ${customer.last_name ?? ""}`.trim() || undefined, phone: customer.phone, ...shipping, }, { idempotencyKey: idempotency_key, }); return { id: stripeCustomer.id, data: stripeCustomer, }; } catch (e) { throw this.buildError("An error occurred in createAccountHolder when creating a Stripe customer", e); } } async updateAccountHolder({ context, }) { const { account_holder, customer, idempotency_key } = context; if (!account_holder?.data?.id) { throw this.buildError("No account holder in context", new Error("No account holder provided while updating account holder")); } // If no customer context was provided, we simply don't update anything within the provider if (!customer) { return {}; } const accountHolderId = account_holder.data.id; const shipping = customer.billing_address ? { address: { city: customer.billing_address.city, country: customer.billing_address.country_code, line1: customer.billing_address.address_1, line2: customer.billing_address.address_2, postal_code: customer.billing_address.postal_code, state: customer.billing_address.province, }, } : undefined; try { const stripeCustomer = await this.stripe_.customers.update(accountHolderId, { email: customer.email, name: customer.company_name || `${customer.first_name ?? ""} ${customer.last_name ?? ""}`.trim() || undefined, phone: customer.phone, ...shipping, }, { idempotencyKey: idempotency_key, }); return { data: stripeCustomer, }; } catch (e) { throw this.buildError("An error occurred in updateAccountHolder when updating a Stripe customer", e); } } async deleteAccountHolder({ context, }) { const { account_holder } = context; const accountHolderId = account_holder?.data?.id; if (!accountHolderId) { throw this.buildError("No account holder in context", new Error("No account holder provided while deleting account holder")); } try { await this.stripe_.customers.del(accountHolderId); return {}; } catch (e) { throw this.buildError("An error occurred in deleteAccountHolder", e); } } async listPaymentMethods({ context, }) { const accountHolderId = context?.account_holder?.data?.id; if (!accountHolderId) { return []; } const paymentMethods = await this.stripe_.customers.listPaymentMethods(accountHolderId, // In order to keep the interface simple, we just list the maximum payment methods, which should be enough in almost all cases. // We can always extend the interface to allow additional filtering, if necessary. { limit: 100 }); return paymentMethods.data.map((method) => ({ id: method.id, data: method, })); } async savePaymentMethod({ context, data, }) { const accountHolderId = context?.account_holder?.data?.id; if (!accountHolderId) { throw this.buildError("Account holder not set while saving a payment method", new Error("Missing account holder")); } const resp = await this.stripe_.setupIntents.create({ customer: accountHolderId, ...data, }, { idempotencyKey: context?.idempotency_key, }); return { id: resp.id, data: resp }; } async getWebhookActionAndData(webhookData) { const event = this.constructWebhookEvent(webhookData); const intent = event.data.object; const { currency } = intent; switch (event.type) { case "payment_intent.created": case "payment_intent.processing": return { action: utils_1.PaymentActions.PENDING, data: { session_id: intent.metadata.session_id, amount: (0, get_smallest_unit_1.getAmountFromSmallestUnit)(intent.amount, currency), }, }; case "payment_intent.canceled": return { action: utils_1.PaymentActions.CANCELED, data: { session_id: intent.metadata.session_id, amount: (0, get_smallest_unit_1.getAmountFromSmallestUnit)(intent.amount, currency), }, }; case "payment_intent.payment_failed": return { action: utils_1.PaymentActions.FAILED, data: { session_id: intent.metadata.session_id, amount: (0, get_smallest_unit_1.getAmountFromSmallestUnit)(intent.amount, currency), }, }; case "payment_intent.requires_action": return { action: utils_1.PaymentActions.REQUIRES_MORE, data: { session_id: intent.metadata.session_id, amount: (0, get_smallest_unit_1.getAmountFromSmallestUnit)(intent.amount, currency), }, }; case "payment_intent.amount_capturable_updated": return { action: utils_1.PaymentActions.AUTHORIZED, data: { session_id: intent.metadata.session_id, amount: (0, get_smallest_unit_1.getAmountFromSmallestUnit)(intent.amount_capturable, currency), }, }; case "payment_intent.succeeded": return { action: utils_1.PaymentActions.SUCCESSFUL, data: { session_id: intent.metadata.session_id, amount: (0, get_smallest_unit_1.getAmountFromSmallestUnit)(intent.amount_received, currency), }, }; default: return { action: utils_1.PaymentActions.NOT_SUPPORTED }; } } /** * Constructs Stripe Webhook event * @param {object} data - the data of the webhook request: req.body * ensures integrity of the webhook event * @return {object} Stripe Webhook event */ constructWebhookEvent(data) { const signature = data.headers["stripe-signature"]; return this.stripe_.webhooks.constructEvent(data.rawData, signature, this.options_.webhookSecret); } buildError(message, error) { const errorDetails = "raw" in error ? error.raw : error; return new Error(`${message}: ${error.message}. ${"detail" in errorDetails ? errorDetails.detail : ""}`.trim()); } } exports.default = StripeBase; //# sourceMappingURL=stripe-base.js.map