Skip to content

feat: stripe saved payment method example #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ To learn how to use an example, open its `README.md` file. You'll find the detai
| [Restock Notification](./restock-notification/README.md) | Allow customers to subscribe to product restock notifications. |
| [Sanity Integration](./sanity-integration/README.md) | Integrate Sanity for rich content-management features. |
| [ShipStation Integration](./shipstation-integration/README.md) | Integrate ShipStation as a fulfillment provider. |
| [Stripe Saved Payment Methods](./stripe-saved-payment/README.md) | Implement saved payment methods with Stripe. |
| [Subscriptions](./subscription/README.md) | Sell subscription-based purchases. |
| [Wishlist Plugin](./wishlist-plugin/README.md) | Allow customers to manage and share wishlists. |

132 changes: 132 additions & 0 deletions stripe-saved-payment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Medusa v2 Example: Saved Payment Methods with Stripe

This directory holds the code for the [Saved Payment Methods with Stripe tutorial](https://docs.medusajs.com/resources/how-to-tutorials/tutorials/saved-payment-methods).

There are two directories:

- `medusa` for the Medusa Application code. You can [install and use it](#installation), or [copy its source files into an existing Medusa application](#copy-into-existing-medusa-application).
- `storefront` for the Next.js Starter storefront with changes for saved payment methods.

## Prerequisites

- [Node.js v20+](https://nodejs.org/en/download)
- [Git CLI](https://git-scm.com/downloads)
- [PostgreSQL](https://www.postgresql.org/download/)
- [Stripe account](https://stripe.com/).
- [Stripe Secret and Public API Keys](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard).

## Medusa Application

### Installation

1. After cloning the repository, change to the `stripe-saved-payment/medusa` directory:

```bash
cd examples/stripe-saved-payment/medusa
```

2\. Rename the `.env.template` file to `.env`, and set the following environment variable:

```bash
STRIPE_API_KEY=sk_123...
```

Where `STRIPE_API_KEY` is the secret Stripe API key.

3\. If necessary, change the PostgreSQL username, password, and host in the `DATABASE_URL` environment variable.

4\. Install dependencies:

```bash
yarn # or npm install
```

5\. Setup and seed the database:

```bash
npx medusa db:setup
yarn seed # or npm run seed
```

6\. Start the Medusa application:

```bash
yarn dev # or npm run dev
```

### Copy into Existing Medusa Application

If you have an existing Medusa application, copy the content of the following directory and file:

1. `src/api/store`
2. `src/api/middlewares.ts`

And make sure to register the Stripe Module Provider in `medusa-config.ts`:

```ts
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@medusajs/medusa/payment-stripe",
id: "stripe",
options: {
apiKey: process.env.STRIPE_API_KEY,
},
},
],
},
},
]
})
```

Set the following environment variable:

```bash
STRIPE_API_KEY=sk_123...
```

Where `STRIPE_API_KEY` is the secret Stripe API key.

## Next.js Storefront

To setup and run the Next.js Storefront:

1. Change to the `stripe-saved-payment/storefront` directory.
2. Copy `.env.template` to `.env.local` and set the following environment variable:

```bash
NEXT_PUBLIC_STRIPE_KEY=pk_123
```

Where `NEXT_PUBLIC_STRIPE_KEY` is Stripe's public API key.

3\. Install the dependencies:

```bash
yarn install # or npm install
```

4\. Start the Next.js storefront (while the Medusa application is running):

```bash
yarn dev # or npm dev
```

## Test it Out

To test out the saved payment methods feature, create a customer account in the Next.js Starter Storefront, add an item to the cart, and go through the checkout flow.

In the payment method, enter the details of a credit card, such as a [test card number](https://docs.stripe.com/testing#cards) and place the order.

Next, add an item to the cart again and proceed through the checkout flow. The card you used earlier will be shown and you can use it to place the order.

## More Resources

- [Medusa Documentation](https://docs.medusajs.com)
- [Stripe Documentation](https://docs.stripe.com)
11 changes: 11 additions & 0 deletions stripe-saved-payment/medusa/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
STORE_CORS=http://localhost:8000,https://docs.medusajs.com
ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
REDIS_URL=redis://localhost:6379
JWT_SECRET=supersecret
COOKIE_SECRET=supersecret
DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary
DB_NAME=medusa-stripe-saved-payment
POSTGRES_URL=

STRIPE_API_KEY=
26 changes: 26 additions & 0 deletions stripe-saved-payment/medusa/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/dist
.env
.DS_Store
/uploads
/node_modules
yarn-error.log

.idea

coverage

!src/**

./tsconfig.tsbuildinfo
medusa-db.sql
build
.cache

.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

.medusa
1 change: 1 addition & 0 deletions stripe-saved-payment/medusa/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
62 changes: 62 additions & 0 deletions stripe-saved-payment/medusa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<p align="center">
<a href="https://www.medusajs.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/59018053/229103275-b5e482bb-4601-46e6-8142-244f531cebdb.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
<img alt="Medusa logo" src="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
</picture>
</a>
</p>
<h1 align="center">
Medusa
</h1>

<h4 align="center">
<a href="https://docs.medusajs.com">Documentation</a> |
<a href="https://www.medusajs.com">Website</a>
</h4>

<p align="center">
Building blocks for digital commerce
</p>
<p align="center">
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
</a>
<a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
<a href="https://discord.gg/xpCwq3Kfn8">
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
</a>
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
</a>
</p>

## Compatibility

This starter is compatible with versions >= 2 of `@medusajs/medusa`.

## Getting Started

Visit the [Quickstart Guide](https://docs.medusajs.com/learn/installation) to set up a server.

Visit the [Docs](https://docs.medusajs.com/learn/installation#get-started) to learn more about our system requirements.

## What is Medusa

Medusa is a set of commerce modules and tools that allow you to build rich, reliable, and performant commerce applications without reinventing core commerce logic. The modules can be customized and used to build advanced ecommerce stores, marketplaces, or any product that needs foundational commerce primitives. All modules are open-source and freely available on npm.

Learn more about [Medusa’s architecture](https://docs.medusajs.com/learn/introduction/architecture) and [commerce modules](https://docs.medusajs.com/learn/fundamentals/modules/commerce-modules) in the Docs.

## Community & Contributions

The community and core team are available in [GitHub Discussions](https://github.com/medusajs/medusa/discussions), where you can ask for support, discuss roadmap, and share ideas.

Join our [Discord server](https://discord.com/invite/medusajs) to meet other community members.

## Other channels

- [GitHub Issues](https://github.com/medusajs/medusa/issues)
- [Twitter](https://twitter.com/medusajs)
- [LinkedIn](https://www.linkedin.com/company/medusajs)
- [Medusa Blog](https://medusajs.com/blog/)
24 changes: 24 additions & 0 deletions stripe-saved-payment/medusa/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Uncomment this file to enable instrumentation and observability using OpenTelemetry
// Refer to the docs for installation instructions: https://docs.medusajs.com/learn/debugging-and-testing/instrumentation

// import { registerOtel } from "@medusajs/medusa"
// // If using an exporter other than Zipkin, require it here.
// import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"

// // If using an exporter other than Zipkin, initialize it here.
// const exporter = new ZipkinExporter({
// serviceName: 'my-medusa-project',
// })

// export function register() {
// registerOtel({
// serviceName: 'medusajs',
// // pass exporter
// exporter,
// instrument: {
// http: true,
// workflows: true,
// query: true
// },
// })
// }
15 changes: 15 additions & 0 deletions stripe-saved-payment/medusa/integration-tests/http/health.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
jest.setTimeout(60 * 1000)

medusaIntegrationTestRunner({
inApp: true,
env: {},
testSuite: ({ api }) => {
describe("Ping", () => {
it("ping the server health endpoint", async () => {
const response = await api.get('/health')
expect(response.status).toEqual(200)
})
})
},
})
3 changes: 3 additions & 0 deletions stripe-saved-payment/medusa/integration-tests/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { MetadataStorage } = require("@mikro-orm/core")

MetadataStorage.clear()
27 changes: 27 additions & 0 deletions stripe-saved-payment/medusa/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { loadEnv } = require("@medusajs/utils");
loadEnv("test", process.cwd());

module.exports = {
transform: {
"^.+\\.[jt]s$": [
"@swc/jest",
{
jsc: {
parser: { syntax: "typescript", decorators: true },
},
},
],
},
testEnvironment: "node",
moduleFileExtensions: ["js", "ts", "json"],
modulePathIgnorePatterns: ["dist/", "<rootDir>/.medusa/"],
setupFiles: ["./integration-tests/setup.js"],
};

if (process.env.TEST_TYPE === "integration:http") {
module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"];
} else if (process.env.TEST_TYPE === "integration:modules") {
module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"];
} else if (process.env.TEST_TYPE === "unit") {
module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"];
}
33 changes: 33 additions & 0 deletions stripe-saved-payment/medusa/medusa-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { loadEnv, defineConfig } from '@medusajs/framework/utils'

loadEnv(process.env.NODE_ENV || 'development', process.cwd())

module.exports = defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
http: {
storeCors: process.env.STORE_CORS!,
adminCors: process.env.ADMIN_CORS!,
authCors: process.env.AUTH_CORS!,
jwtSecret: process.env.JWT_SECRET || "supersecret",
cookieSecret: process.env.COOKIE_SECRET || "supersecret",
}
},
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@medusajs/medusa/payment-stripe",
id: "stripe",
options: {
apiKey: process.env.STRIPE_API_KEY,
},
},
],
},
},
],

})
Loading