Skip to content
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
10 changes: 10 additions & 0 deletions examples/ghost-checkout/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# MoneyDevKit Configuration
MDK_ACCESS_TOKEN=your_mdk_access_token
MDK_MNEMONIC=your_12_or_24_word_mnemonic

# Ghost Integration
GHOST_URL=https://yourblog.ghost.io
GHOST_ADMIN_API_KEY=your_ghost_admin_api_key

# Optional: Default redirect after successful payment
SUCCESS_URL=https://yourblog.com/thank-you
95 changes: 95 additions & 0 deletions examples/ghost-checkout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Ghost Checkout

Accept Lightning payments for your Ghost blog memberships.

## One-Click Deploy

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmoneydevkit%2Fmdk-checkout%2Ftree%2Fmdk-216%2Fexamples%2Fghost-checkout&env=MDK_ACCESS_TOKEN,MDK_MNEMONIC,GHOST_URL,GHOST_ADMIN_API_KEY&envDescription=MoneyDevKit%20and%20Ghost%20credentials&project-name=ghost-checkout&repository-name=ghost-checkout)

## Environment Variables

| Variable | Description |
|----------|-------------|
| `MDK_ACCESS_TOKEN` | Your MoneyDevKit access token |
| `MDK_MNEMONIC` | Your 12 or 24 word mnemonic for the Lightning wallet |
| `GHOST_URL` | Your Ghost site URL (e.g., `https://yourblog.ghost.io`) |
| `GHOST_ADMIN_API_KEY` | Ghost Admin API key (format: `id:secret`) |

## How It Works

1. Generate a signed checkout URL using `createCheckoutUrl`
2. Add the URL to your Ghost site with a `data-mdk` attribute
3. JavaScript injects the member's email into the URL
4. When a user pays, their Ghost membership is automatically updated

## Ghost Setup

### Step 1: Get a checkout link

1. Go to [moneydevkit.com](https://moneydevkit.com) and sign in
2. Create a subscription with your desired price
3. Copy the checkout link for your subscription

### Step 2: Add the script to Ghost

Go to **Settings > Code injection > Site Footer** and add:

```html
<script>
(function () {
// ========== CONFIGURATION ==========
var CHECKOUT_URL = 'http://localhost:3004/api/mdk?action=createCheckout&checkoutPath=%2Fcheckout&product=cmkmuzney000pad100adrw8qf&type=PRODUCTS&signature=b2dfa68d36fb40d7b836e16349d20e2fa822315e8d490dc5a0fe4c207ad92742';
var GHOST_URL = 'https://mdktest.ghost.io';
var BUTTON_TEXT = '⚡ Subscribe with Lightning';
// ===================================

var memberData = null;

function getUrl(){
var url = CHECKOUT_URL;
url += '&successUrl=' + encodeURIComponent(window.location.href);
if (memberData) {
url += '&customer=' + encodeURIComponent(JSON.stringify(memberData));
}
return url;
}

function replace() {
var btn = document.querySelector('.gh-post-upgrade-cta a.gh-btn:not([data-mdk])');
if (!btn) return;
var newBtn = document.createElement('a');
newBtn.className = btn.className;
newBtn.style.cssText = btn.style.cssText;
newBtn.setAttribute('data-mdk', '1');
newBtn.href = getUrl();
newBtn.textContent = BUTTON_TEXT;
newBtn.onclick = function (e) {
if (newBtn.getAttribute('data-loading')) {
e.preventDefault();
return false;
}
newBtn.setAttribute('data-loading', '1');
newBtn.textContent = 'Loading...';
newBtn.style.opacity = '0.7';
newBtn.style.pointerEvents = 'none';
window.location.href = getUrl();
return false;
};
btn.parentNode.replaceChild(newBtn, btn);
}

// Only activate if member is logged in
fetch(GHOST_URL + '/members/api/member/', { credentials: 'include' })
.then(function (res) { return res.ok ? res.json() : null; })
.then(function (member) {
if (member && member.email) {
memberData = { email: member.email };
if (member.uuid) memberData.externalId = member.uuid;
// Start replacing buttons only after we have member data
setInterval(replace, 100);
}
})
.catch(function () {});
})();
</script>
```
6 changes: 6 additions & 0 deletions examples/ghost-checkout/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
3 changes: 3 additions & 0 deletions examples/ghost-checkout/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import withMdkCheckout from '@moneydevkit/nextjs/next-plugin'

export default withMdkCheckout({})
27 changes: 27 additions & 0 deletions examples/ghost-checkout/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "ghost-checkout",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@moneydevkit/ghost": "^0.7.0-alpha.1",
"@moneydevkit/lightning-js": "^0.1.60",
"next": "^15.3.5",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"optionalDependencies": {
"@moneydevkit/lightning-js-linux-x64-gnu": "^0.1.60"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"typescript": "^5"
}
}
2 changes: 2 additions & 0 deletions examples/ghost-checkout/src/app/api/mdk/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Use the ghost package route handler which adds Ghost sync on payment success
export { POST, GET } from '@moneydevkit/ghost/server/route'
15 changes: 15 additions & 0 deletions examples/ghost-checkout/src/app/checkout/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Checkout } from '@moneydevkit/ghost'

interface CheckoutPageProps {
params: Promise<{ id: string }>
}

export default async function CheckoutPage({ params }: CheckoutPageProps) {
const { id } = await params

return (
<main className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
<Checkout id={id} />
</main>
)
}
24 changes: 24 additions & 0 deletions examples/ghost-checkout/src/app/checkout/error/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
interface ErrorPageProps {
searchParams: Promise<{ error?: string; message?: string }>
}

export default async function ErrorPage({ searchParams }: ErrorPageProps) {
const { error, message } = await searchParams

return (
<main className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8 text-center">
<div className="text-red-500 text-5xl mb-4">⚠️</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">
{error === 'invalid_signature' ? 'Invalid Checkout Link' : 'Checkout Error'}
</h1>
<p className="text-gray-600 mb-6">
{message || 'Something went wrong with your checkout link.'}
</p>
<p className="text-sm text-gray-500">
Please request a new payment link from the site.
</p>
</div>
</main>
)
}
7 changes: 7 additions & 0 deletions examples/ghost-checkout/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
20 changes: 20 additions & 0 deletions examples/ghost-checkout/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Metadata } from 'next'
import '@moneydevkit/nextjs/mdk-styles.css'
import './globals.css'

export const metadata: Metadata = {
title: 'Ghost Checkout',
description: 'Lightning payments for Ghost memberships',
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
17 changes: 17 additions & 0 deletions examples/ghost-checkout/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default function Home() {
return (
<main className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8 text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">
Ghost Checkout
</h1>
<p className="text-gray-600 mb-6">
This is a MoneyDevKit checkout server for Ghost blogs.
</p>
<p className="text-sm text-gray-500">
Payments are handled via signed checkout URLs from your Ghost site.
</p>
</div>
</main>
)
}
27 changes: 27 additions & 0 deletions examples/ghost-checkout/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
6 changes: 6 additions & 0 deletions examples/ghost-checkout/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "npm run build",
"installCommand": "npm install",
"framework": "nextjs"
}
Loading