Skip to content

Commit 3a1ed74

Browse files
feat(payment-stripe): OXXO payment provider support with configurable expiration (medusajs#13805)
## Summary **What** — What changes are introduced in this PR? This pull request adds support for OXXO payments to the Stripe payment provider module. The main changes include the addition of a new `OxxoProviderService`, updates to type definitions to support OXXO-specific options, and integration of the new service into the provider's exports and registration. **Why** — Why are these changes relevant or necessary? I was testing the MedusaJs server, I live in México and here oxxo as a Payment method is very used. **How** — How have these changes been implemented? * Introduced `OxxoProviderService` in `stripe-oxxo.ts`, which extends `StripeBase` and configures Stripe payment intents for OXXO, including expiration settings. * Updated `StripeOptions` and `PaymentIntentOptions` types to include `oxxoExpiresDays` and OXXO-specific payment method options for intent configuration. * Added `OXXO` to the `PaymentProviderKeys` enum for provider identification. **Testing** — How have these changes been tested, or how can the reviewer test the feature? You need to launch the medusa server, add Stripe OXXO as Payment Provider in your Store Region (OXXO only works in México and with mxn currency) and set payment_method_data type to oxxo. Also you need to allow oxxo payment as payment method in stripe and configure your stripe webhook to your medusa server --- ## Examples Provide examples or code snippets that demonstrate how this feature works, or how it can be used in practice. This helps with documentation and ensures maintainers can quickly understand and verify the change. ```ts // Example usage using nextjs starter const confirmParams: any = { return_url: returnUrl, } if (isOxxo(providerId)) { confirmParams.payment_method_data = { type: "oxxo", billing_details: { name: cart.billing_address?.first_name + " " + cart.billing_address?.last_name, address: { city: cart.billing_address?.city ?? undefined, country: cart.billing_address?.country_code ?? undefined, line1: cart.billing_address?.address_1 ?? undefined, line2: cart.billing_address?.address_2 ?? undefined, postal_code: cart.billing_address?.postal_code ?? undefined, state: cart.billing_address?.province ?? undefined, }, email: cart.email, phone: cart.billing_address?.phone ?? undefined, }, } await stripe .confirmPayment({ clientSecret, confirmParams, redirect: "if_required", }) .then(({ error, paymentIntent }) => { console.log({ error, paymentIntent }) const validateIntent = async (paymentIntent: any) => { const link = paymentIntent.next_action?.oxxo_display_details ?.hosted_voucher_url if (link) { setSubmitting(false) setMessage( "Se ha generado un cupón de pago de OXXO. Por favor, revisa la nueva pestaña abierta." ) await onPaymentCompleted() // Here I call the function because I have custom logic for creating a pending order } } if (error) { const pi = error.payment_intent if ( (pi && pi.status === "requires_capture") || (pi && pi.status === "succeeded") ) { onPaymentCompleted() } if (pi && pi.status === "requires_action") { validateIntent(pi) return } setErrorMessage(error.message || null) setSubmitting(false) return } if ( (paymentIntent && paymentIntent.status === "requires_capture") || (paymentIntent && paymentIntent.status === "succeeded") ) { return onPaymentCompleted() } if (paymentIntent && paymentIntent.status === "requires_action") { validateIntent(paymentIntent) // This is the action that you normally get } }) } } // Configuration on the server (medusa-config.ts) modules: [ { resolve: "@medusajs/medusa/payment", options: { providers: [ { resolve: "@medusa/payment-stripe", id: "stripe", options: { apiKey: process.env.STRIPE_API_KEY, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, capture: true, oxxoExpiresDays: 7, // default to 3 }, }, ], }, }, ], // And not necessary buy you can extend even more creating a pending-orders module (for showing the vouchers created that costumer need to pay in the frontend, because if not the order only creates after the user have payed the voucher, for testing is 3 minutes), this is an example model: import { model } from "@medusajs/framework/utils"; export const PendingOrder = model.define("pending_order", { id: model.id().primaryKey(), cart_id: model.text().unique(), user_id: model.text(), total: model.number(), payment_type: model.text(), voucher_url: model.text().nullable(), payment_session_id: model.text(), // this are the ones that works to identify the payment payment_collection_id: model.text(), // this are the ones that works to identify the payment }); export default PendingOrder; ``` --- ## Checklist Please ensure the following before requesting a review: - [x] I have added a **changeset** for this PR - Every non-breaking change should be marked as a **patch** - To add a changeset, run `yarn changeset` and follow the prompts - [x] The changes are covered by relevant **tests** - [x] I have verified the code works as intended locally - [x ] I have linked the related issue(s) if applicable --- ## Additional Context medusajs#13804 --- > [!NOTE] > Adds OXXO payment support to the Stripe provider with configurable expiration days and updates intent option handling and typings. > > - **Payment Providers**: > - **New `OxxoProviderService`** (`services/stripe-oxxo.ts`): configures `payment_intent` for OXXO with `expires_after_days` (defaults to `3`, configurable via `options.oxxoExpiresDays`). > - Registered in `src/index.ts` and exported from `services/index.ts`. > - **Types**: > - `StripeOptions` adds `oxxoExpiresDays`. > - `PaymentIntentOptions` adds `payment_method_options.oxxo.expires_after_days`. > - `PaymentProviderKeys` adds `OXXO`. > - **Core**: > - `core/stripe-base.ts`: `normalizePaymentIntentParameters` now falls back to `this.paymentIntentOptions.payment_method_options` when not provided in `extra`. > - **Changeset**: > - Patch release for `@medusajs/payment-stripe`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4d7fe06. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> Co-authored-by: William Bouchard <[email protected]>
1 parent 62d103b commit 3a1ed74

File tree

6 files changed

+45
-2
lines changed

6 files changed

+45
-2
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@medusajs/payment-stripe": patch
3+
---
4+
5+
feat(payment-stripe): OXXO payment provider support with configurable expiration

packages/modules/providers/payment-stripe/src/core/stripe-base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
114114
extra?.payment_method_data as Stripe.PaymentIntentCreateParams.PaymentMethodData
115115

116116
res.payment_method_options =
117-
extra?.payment_method_options as Stripe.PaymentIntentCreateParams.PaymentMethodOptions
117+
(extra?.payment_method_options as Stripe.PaymentIntentCreateParams.PaymentMethodOptions) ??
118+
this.paymentIntentOptions.payment_method_options
118119

119120
res.automatic_payment_methods =
120121
(extra?.automatic_payment_methods as { enabled: true } | undefined) ??

packages/modules/providers/payment-stripe/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
StripeProviderService,
88
StripePrzelewy24Service,
99
StripePromptpayService,
10+
OxxoProviderService,
1011
} from "./services"
1112

1213
const services = [
@@ -17,6 +18,7 @@ const services = [
1718
StripeProviderService,
1819
StripePrzelewy24Service,
1920
StripePromptpayService,
21+
OxxoProviderService,
2022
]
2123

2224
export default ModuleProvider(Modules.PAYMENT, {

packages/modules/providers/payment-stripe/src/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { default as StripeIdealService } from "./stripe-ideal"
55
export { default as StripeProviderService } from "./stripe-provider"
66
export { default as StripePrzelewy24Service } from "./stripe-przelewy24"
77
export { default as StripePromptpayService } from "./stripe-promptpay"
8+
export { default as OxxoProviderService } from "./stripe-oxxo"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import StripeBase from "../core/stripe-base"
2+
import { PaymentIntentOptions, PaymentProviderKeys } from "../types"
3+
4+
class OxxoProviderService extends StripeBase {
5+
static identifier = PaymentProviderKeys.OXXO
6+
7+
constructor(_, options) {
8+
super(_, options)
9+
}
10+
11+
get paymentIntentOptions(): PaymentIntentOptions {
12+
return {
13+
payment_method_types: ["oxxo"],
14+
capture_method: "automatic",
15+
payment_method_options: {
16+
oxxo: {
17+
expires_after_days: this.options.oxxoExpiresDays || 3,
18+
},
19+
},
20+
}
21+
}
22+
}
23+
24+
export default OxxoProviderService

packages/modules/providers/payment-stripe/src/types/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ export interface StripeOptions {
1919
* Set a default description on the intent if the context does not provide one
2020
*/
2121
paymentDescription?: string
22+
/**
23+
* Set the number of days before an OXXO payment expires
24+
*/
25+
oxxoExpiresDays?: number
2226
}
2327

2428
export interface PaymentIntentOptions {
2529
capture_method?: "automatic" | "manual"
2630
setup_future_usage?: "on_session" | "off_session"
2731
payment_method_types?: string[]
32+
payment_method_options?: {
33+
oxxo?: {
34+
expires_after_days?: number
35+
}
36+
}
2837
}
2938

3039
export const ErrorCodes = {
@@ -38,10 +47,11 @@ export const ErrorIntentStatus = {
3847

3948
export const PaymentProviderKeys = {
4049
STRIPE: "stripe",
50+
OXXO: "stripe-oxxo",
4151
BAN_CONTACT: "stripe-bancontact",
4252
BLIK: "stripe-blik",
4353
GIROPAY: "stripe-giropay",
4454
IDEAL: "stripe-ideal",
4555
PRZELEWY_24: "stripe-przelewy24",
46-
PROMPT_PAY : "stripe-promptpay",
56+
PROMPT_PAY: "stripe-promptpay",
4757
}

0 commit comments

Comments
 (0)