Skip to content
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

Medusa V2 Support #156

Merged
merged 26 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
257d6bf
wip: v2 migration
a11rew Oct 28, 2024
070d947
feat: migrate webhook handling behavior
a11rew Oct 30, 2024
b9a3560
fix: upgrade tsconfig to support exports resolution
a11rew Nov 4, 2024
8d131d0
feat: upgrade provider service
a11rew Nov 4, 2024
3a87014
feat: make email a required initialization parameter
a11rew Nov 5, 2024
80ce292
feat: expect sessionId instead of cartId in webhook event
a11rew Nov 5, 2024
f6dedc2
docs: update configuration instructions
a11rew Nov 5, 2024
41f0cae
chore: pre-release versioning
a11rew Nov 5, 2024
2d3f962
fix: send amount in subunit
a11rew Nov 5, 2024
3864dad
chore: version
a11rew Nov 5, 2024
46fe1de
feat: migrate tests
a11rew Nov 10, 2024
5a103ba
refactor: remove @medusajs/types dep infavor of @medusajs/framework/t…
a11rew Nov 10, 2024
3c78db8
Merge branch 'main' of github.com:a11rew/medusa-payment-paystack into…
a11rew Nov 10, 2024
a002cec
chore: upgrade lockfile
a11rew Nov 10, 2024
e111bb1
chore: remove deprecated deps
a11rew Nov 10, 2024
925b521
chore: simplify tsconfig and build process
a11rew Nov 10, 2024
ebac85b
feat: upgrade workflows
a11rew Nov 10, 2024
c6a7691
feat: return access code and authorization url as first class citizens
a11rew Nov 10, 2024
7e73061
chore: add changeset
a11rew Nov 10, 2024
ce4dd52
fix: output declaration files
a11rew Nov 10, 2024
f5cbd3a
docs: update README with v2 instructions
a11rew Nov 10, 2024
f98dcc7
docs: update storefront integration example code
a11rew Nov 17, 2024
4670db7
chore: exit prerelease mode and version packages
a11rew Nov 17, 2024
8bd2264
docs: add Paystack obj import to example
a11rew Nov 17, 2024
22e33c6
docs: update examples section
a11rew Nov 17, 2024
bfd2002
fix: remove prepare script
a11rew Nov 17, 2024
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
31 changes: 0 additions & 31 deletions .github/workflows/dependabot-lock-file.yml

This file was deleted.

46 changes: 8 additions & 38 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@ on:
branches: [main]

jobs:
packages:
name: Build & Lint
plugin:
name: Build & Lint Plugin
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/plugin
steps:
- name: Checkout Repo
uses: actions/checkout@v3

- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8.6.11
version: 9

- name: Setup Node.js 18
- name: Setup Node.js 20
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: "pnpm"

- name: Install Dependencies
Expand All @@ -32,39 +35,6 @@ jobs:
run: pnpm run build
env:
NODE_ENV: production
NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY: ci

- name: Lint
run: pnpm run lint

examples:
name: Build & Lint Examples
runs-on: ubuntu-latest
strategy:
matrix:
directory: [examples/storefront]
defaults:
run:
working-directory: ${{ matrix.directory }}
steps:
- name: Checkout Repo
uses: actions/checkout@v3

- name: Setup Node.js 18
uses: actions/setup-node@v3
with:
node-version: 18
cache: "yarn"
cache-dependency-path: ${{ matrix.directory }}/yarn.lock

- name: Install Dependencies
run: yarn install --frozen-lockfile

- name: Build
run: yarn next experimental-compile # Skip static page generation
env:
NODE_ENV: production
NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY: ci

- name: Lint
run: yarn lint
9 changes: 6 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@ jobs:
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8.6.11
version: 9

- name: Setup Node.js 18
- name: Setup Node.js 20
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: "pnpm"

- name: Install Dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm run build

- name: Create Release Pull Request
uses: changesets/action@v1
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ jobs:
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8.6.11
version: 9

- name: Setup Node.js 18
- name: Setup Node.js 20
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: "pnpm"

- name: Install Dependencies
Expand Down
192 changes: 133 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

## Medusa Server

If you don’t have a Medusa server installed yet, you must follow the [quickstart guide](https://docs.medusajs.com/quickstart/quick-start/) first.
If you don’t have a Medusa server installed yet, you must follow the [quickstart guide](https://docs.medusajs.com/learn) first.

### Install the Paystack Plugin

In the root of your Medusa server, run the following command to install the Paystack plugin:
In the root of your Medusa server (backend), run the following command to install the Paystack plugin:

```bash
yarn add medusa-payment-paystack
Expand All @@ -30,19 +30,32 @@ yarn add medusa-payment-paystack

Next, you need to enable the plugin in your Medusa server.

In `medusa-config.js` add the following to the `plugins` array:
In `medusa-config.ts` add the following to the `plugins` array:

```js
const plugins = [
// other plugins
{
resolve: `medusa-payment-paystack`,
/** @type {import("medusa-payment-paystack").PluginOptions} */
options: {
secret_key: "<PAYSTACK_SECRET_KEY>",
},
```ts
module.exports = defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
// ... other config
},
];
modules: [
// other modules
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
// other payment providers like stripe, paypal etc
{
resolve: "medusa-payment-paystack",
options: {
secret_key: <PAYSTACK_SECRET_KEY>,
} satisfies import("medusa-payment-paystack").PluginOptions,
},
],
},
},
],
});
```

The full list of configuration options you can pass to the plugin can be found in [Config](#configuration)
Expand All @@ -53,88 +66,149 @@ To ensure that Medusa is notified of successful payments, you need to set up web

Go to your [Paystack dashboard](https://dashboard.paystack.com/#/settings/developer) and navigate to the "API Keys & Webhooks" section.

Set the Webhook URL to `<your-medusa-backend-url>/paystack/hooks`. Eg. `https://your-medusa-backend.com/paystack/hooks`.
Set the Webhook URL to `<your-medusa-backend-url>/hooks/payment/paystack`. Eg. `https://your-medusa-backend.com/hooks/payment/paystack`.

## Admin Setup

This step is required for you to be able to use Paystack as a payment provider in your storefront.

### Admin Prerequisites

If you don’t have a Medusa admin installed, make sure to follow [the guide on how to install it](https://github.com/medusajs/admin#-quickstart) before continuing with this section.

### Add Paystack to Regions

You can refer to [this documentation in the user guide](https://docs.medusajs.com/user-guide/regions/providers/#manage-payment-providers) to learn how to add a payment provider like Paystack to a region.
Refer to [this documentation in the user guide](https://docs.medusajs.com/v1/user-guide/regions/providers/#manage-payment-providers) to learn how to add a payment provider like Paystack to a region.

## Storefront Setup

Follow Medusa's [Checkout Flow](https://docs.medusajs.com/advanced/storefront/how-to-implement-checkout-flow/) guide using `paystack` as the `provider_id` to add Paystack to your checkout flow.
Follow Medusa's [Storefront Development Checkout Flow](https://docs.medusajs.com/resources/storefront-development/checkout/payment) guide using `pp_paystack` as the `provider_id` to add Paystack to your checkout flow.

`medusa-payment-paystack` returns a transaction reference you should send to Paystack as the transaction's reference.
### Email in `initiatePaymentSession` context

Using this returned reference as the Paystack transaction's reference allows the plugin to confirm the status of the transaction, verify that the paid amount and currency are correct before authorizing the payment.
Paystack requires the customer's email address to create a transaction.

### Using Transaction Reference
You **need** to pass the customer's email address in the `initiatePaymentSession` context to create a transaction.

`medusa-payment-paystack` inserts a reference named `paystackTxRef` into the [`PaymentSession`](https://docs.medusajs.com/advanced/backend/payment/overview/#payment-session)'s data.
If your storefront does not collect customer email addresses, you can provide a dummy email but be aware all transactions on your Paystack dashboard will be associated with that email address.

```js
const { paystackTxRef } = paymentSession.data;
```ts
await initiatePaymentSession(cart, {
provider_id: selectedPaymentMethod,
context: {
email: cart.email,
},
});
```

Provide this reference when initiating the Paystack [Popup](https://paystack.com/docs/guides/migrating-from-inlinejs-v1-to-v2/) payment flow.
### Completing the Payment Flow

```js
const paymentForm = document.getElementById("paymentForm");
paymentForm.addEventListener("submit", payWithPaystack, false);
`medusa-payment-paystack` returns an access code and authorization URL that you should use to complete the Paystack payment flow on the storefront.

function payWithPaystack(e) {
e.preventDefault();
Using the returned access code and authorization URL allows the plugin to confirm the status of the transaction on your backend, and then relay that information to Medusa.

const paystack = new PaystackPop();
`medusa-payment-paystack` inserts the access code (`paystackTxAccessCode`) and authorization URL (`paystackTxAuthorizationUrl`) into the [`PaymentSession`](https://docs.medusajs.com/advanced/backend/payment/overview/#payment-session)'s data.

paystack.newTransaction({
key: "pk_test_xxxxxxxxxx", // Your Paystack public key
email: document.getElementById("email-address").value,
amount: document.getElementById("amount").value, // Value in lowest denomination of currency to be paid
ref: paystackTxRef, // Reference gotten from plugin
onSuccess() {
// Call Medusa checkout complete here
},
onCancel() {
alert("Window closed.");
},
});
You can use the access code to resume the payment flow, or the authorization URL to redirect the customer to Paystack's hosted payment page.

#### Using Access Code

Extract the access code from the payment session's data:

```js
const { paystackTxAccessCode } = paymentSession.data;
```

Provide this access code to the `resumeTransaction` method from Paystack's [InlineJS](https://paystack.com/docs/guides/migrating-from-inlinejs-v1-to-v2/) library.

```ts
import Paystack from "@paystack/inline-js"

const PaystackPaymentButton = ({
session,
notReady,
}: {
session: HttpTypes.StorePaymentSession | undefined
notReady: boolean
}) => {
const paystackRef = useRef<Paystack | null>(null)

// If the session is not ready, we don't want to render the button
if (notReady || !session) return null

// Get the accessCode added to the session data by the Paystack plugin
const accessCode = session.data.paystackTxAccessCode
if (!accessCode) throw new Error("Transaction access code is not defined")

return (
<button
onClick={() => {
if (!paystackRef.current) {
paystackRef.current = new Paystack()
}

const paystack = paystackRef.current

paystack.resumeTransaction(accessCode, {
async onSuccess() {
// Call Medusa checkout complete here
await placeOrder()
},
onError(error: unknown) {
// Handle error
},
})
}}
>
Pay with Paystack
</button>
)
}
```

#### Using Authorization URL

As a pre-requisite, you must have configured a "Callback URL" in your Paystack dashboard. Follow [this guide](https://support.paystack.com/en/articles/2129538) to set it up.

The callback URL can be a custom route on your Medusa backend, it can be a page in your storefront or a view in your mobile application. That route just needs to call the Medusa [Complete Cart](https://docs.medusajs.com/resources/storefront-development/checkout/complete-cart) method.

Extract the authorization URL from the payment session's data:

```ts
const { paystackTxAuthorizationUrl } = session.data;
```

Redirect the customer to the authorization URL to complete the payment.

```ts
// Redirect the customer to Paystack's hosted payment page
window.open(paystackTxAuthorizationUrl, "_self");
```

Once the payment is successful, the customer will be redirected back to the callback URL. This page can then call the Medusa [Complete Cart](https://docs.medusajs.com/resources/storefront-development/checkout/complete-cart) method to complete the checkout flow and show a success message to the customer.

### Verify Payment

Call the Medusa [Complete Cart](https://docs.medusajs.com/advanced/storefront/how-to-implement-checkout-flow/#complete-cart) method in the payment completion callback of your chosen flow.
Call the Medusa [Complete Cart](https://docs.medusajs.com/resources/storefront-development/checkout/complete-cart) method in the payment completion callback of your chosen flow as mentioned in [Completing the Payment Flow](#completing-the-payment-flow) above.

`medusa-payment-paystack` will verify the transaction with Paystack and mark the cart as paid for in Medusa.

`medusa-payment-paystack` will check the status of the transaction with the reference it provided you, verify the amount matches the cart total and mark the cart as paid for in Medusa.
Even if the "Complete Cart" method is not called for any reason, with webhooks set up correctly, the transaction will still be marked as paid for in Medusa when the user pays for it.

## Refund Payments

You can refund captured payments made with Paystack from the Admin dashboard.

`medusa-payment-paystack` handles refunding the given amount using Paystack and marks the order in Medusa as refunded.

# Configuration

| Name | Type | Default | Description |
| --------------- | --------- | ----------- | --------------------------------------------------------------------------------------------- |
| secret_key | `string` | `undefined` | Your Paystack secret key |
| disable_retries | `boolean` | `false` | Disable retries for 5xx and failed idempotent requests to Paystack |
| debug | `boolean` | `false` | Enable debug mode for the plugin. If true, helpful debug information is logged to the console |
Partial refunds are also supported.

# Demo
# Configuration

Try out the demo here: [Medusa store with Paystack Integration](https://storefront-production-ae6c.up.railway.app/)
| Name | Type | Default | Description |
| --------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| secret_key | `string` | - | Your Paystack secret key. Should be in the format sk_test-... or sk_live-... Obtainable from the Paystack dashboard - Settings -> API Keys & Webhooks. |
| disable_retries | `boolean` | `false` | Disable retries on network errors and 5xx errors on idempotent requests to Paystack. Generally, you should not disable retries, these errors are usually temporary but it can be useful for debugging. |
| debug | `boolean` | `false` | Enable debug mode for the plugin. If true, logs helpful debug information to the console. Logs are prefixed with "PS_P_Debug". |

![Demo video](https://user-images.githubusercontent.com/87580113/211937892-d1a34735-78a5-451d-83f8-bc23185dd8ef.png)
# Examples

[Demo Video](https://vimeo.com/763132960)
The [`examples`](https://github.com/a11rew/medusa-payment-paystack/blob/main/examples) directory contains a simple Medusa server with the Paystack plugin installed and configured.

Clone the demo repository [a11rew/medusa-payment-paystack-demo](https://github.com/a11rew/medusa-paystack-demo) and follow the [setup instructions](https://github.com/a11rew/medusa-paystack-demo#set-up-project) to get started.
It also contains a storefront built with Next.js that uses the inline-js Paystack library to complete the payment flow.
Loading
Loading