Skip to content
Draft
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
3 changes: 3 additions & 0 deletions apps/platform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
"@muqa/db": "workspace:*",
"@next-auth/prisma-adapter": "^1.0.7",
"@react-google-maps/api": "^2.19.3",
"@stripe/crypto": "^0.0.4",
"@stripe/stripe-js": "^4.5.0",
"axios": "^1.7.4",
"ethers": "^6.13.2",
"next": "14.2.2",
"next-auth": "^4.24.7",
"next-intl": "^3.17.2",
"react": "18",
"react-dom": "18",
"stripe": "^16.12.0",
"wagmi": "2.9.0",
"zod": "^3.23.8"
},
Expand Down
82 changes: 82 additions & 0 deletions apps/platform/src/app/api/create-onramp-session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Stripe from 'stripe';
import { NextResponse } from 'next/server';

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};

const OnrampSessionResource = Stripe.StripeResource.extend({
create: Stripe.StripeResource.method({
method: 'POST',
path: 'crypto/onramp_sessions',
}),
});

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
apiVersion: '2024-06-20',
});

export async function OPTIONS() {
return NextResponse.json({}, { headers: corsHeaders });
}

export async function POST(req: Request, { params }: { params: any }) {
const { transaction_details } = await req.json();

let clientSecret = '';

const apiKey = process.env.STRIPE_SECRET_KEY || '';
const url = 'https://api.stripe.com/v1/crypto/onramp_sessions';

const requestData = new URLSearchParams();
requestData.append('customer_ip_address', '8.8.8.8');
// requestData.append(
// 'wallet_addresses[solana]',
// '0x495A28448A06B0DF634750EB062311dDC40B3ae5',
// );
// requestData.append('destination_networks[]', 'solana');
// requestData.append('destination_currencies[]', 'usdc');
// requestData.append('destination_network', 'solana');
// requestData.append('destination_currency', 'usdc');
// requestData.append('destination_amount', '10');
requestData.append(
'wallet_addresses[ethereum]',
'0x495A28448A06B0DF634750EB062311dDC40B3ae5',
);
requestData.append('destination_networks[]', 'ethereum');
requestData.append('destination_currencies[]', 'usdc');
requestData.append('destination_network', 'ethereum');
requestData.append('destination_currency', 'usdc');
requestData.append('destination_amount', '6');

const headers = {
Authorization: `Basic ${Buffer.from(apiKey + ':').toString('base64')}`,
'Content-Type': 'application/x-www-form-urlencoded',
};

const requestOptions = {
method: 'POST',
headers: headers,
body: requestData,
};

await fetch(url, requestOptions)
.then(response => response.json())
.then(data => {
clientSecret = data.client_secret;
})
.catch(error => {
console.error('Error creating onramp session:', error);
});

return NextResponse.json(
{
clientSecret: clientSecret,
},
{
headers: corsHeaders,
},
);
}
89 changes: 89 additions & 0 deletions apps/platform/src/app/stripe/StripeCryptoElements.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';

// ReactContext to simplify access of StripeOnramp object
const CryptoElementsContext = React.createContext(null);
CryptoElementsContext.displayName = 'CryptoElementsContext';

export const CryptoElements = ({ stripeOnramp, children }: any) => {
const [ctx, setContext] = React.useState(() => ({
onramp: null,
}));

React.useEffect(() => {
let isMounted = true;

Promise.resolve(stripeOnramp).then((onramp) => {
if (onramp && isMounted) {
setContext((ctx) => (ctx.onramp ? ctx : { onramp }));
}
});

return () => {
isMounted = false;
};
}, [stripeOnramp]);

return (
<CryptoElementsContext.Provider value={ctx as any}>
{children}
</CryptoElementsContext.Provider>
);
};

// React hook to get StripeOnramp from context
export const useStripeOnramp = () => {
const context = React.useContext(CryptoElementsContext) as any;
return context?.onramp;
};

// React element to render Onramp UI
const useOnrampSessionListener = (type: any, session: any, callback: any) => {
React.useEffect(() => {
if (session && callback) {
const listener = (e: { payload: any; }) => callback(e.payload);
session.addEventListener(type, listener);
return () => {
session.removeEventListener(type, listener);
};
}
return () => {};
}, [session, callback, type]);
};

export const OnrampElement = ({
clientSecret,
appearance,
onReady,
onChange,
...props
}: any) => {
const stripeOnramp = useStripeOnramp();
const onrampElementRef = React.useRef(null);
const [session, setSession] = React.useState();

const appearanceJSON = JSON.stringify(appearance);
React.useEffect(() => {
const containerRef = onrampElementRef.current as any;
if (containerRef) {
// NB: ideally we want to be able to hot swap/update onramp iframe
// This currently results a flash if one needs to mint a new session when they need to udpate fixed transaction details
containerRef.innerHTML = '';

if (clientSecret && stripeOnramp) {
setSession(
stripeOnramp
.createSession({
clientSecret,
appearance: appearanceJSON ? JSON.parse(appearanceJSON) : {},
})
.mount(containerRef)
);
}
}
}, [appearanceJSON, clientSecret, stripeOnramp]);

useOnrampSessionListener('onramp_ui_loaded', session, onReady);
useOnrampSessionListener('onramp_session_updated', session, onChange);

return <div {...props} ref={onrampElementRef}></div>;
};
77 changes: 77 additions & 0 deletions apps/platform/src/app/stripe/StripeCryptoElementsOLD.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use client';

import React, { ReactNode } from 'react';

const CryptoElementsContext = React.createContext<{ onramp: any } | null>(null);

interface CryptoElementsProps {
stripeOnramp: any;
children: ReactNode;
}

export const CryptoElements: React.FC<CryptoElementsProps> = ({
stripeOnramp,
children,
}) => {
const [ctx, setContext] = React.useState(() => ({ onramp: null }));

React.useEffect(() => {
let isMounted = true;

Promise.resolve(stripeOnramp).then(onramp => {
if (onramp && isMounted) {
setContext(ctx => (ctx.onramp ? ctx : { onramp }));
}
});

return () => {
isMounted = false;
};
}, [stripeOnramp]);

return (
<CryptoElementsContext.Provider value={ctx}>
{children}
</CryptoElementsContext.Provider>
);
};

// React hook to get StripeOnramp from context
export const useStripeOnramp = () => {
const context = React.useContext(CryptoElementsContext);
return context?.onramp;
};

// React element to render Onramp UI
interface OnrampElementProps {
clientSecret: string;
appearance: any; // Replace 'any' with the appropriate type if known
[key: string]: any;
}

export const OnrampElement: React.FC<OnrampElementProps> = ({
clientSecret,
appearance,
...props
}) => {
const stripeOnramp = useStripeOnramp();
const onrampElementRef = React.useRef<HTMLDivElement>(null);

React.useEffect(() => {
const containerRef = onrampElementRef.current;
if (containerRef) {
containerRef.innerHTML = '';

if (clientSecret && stripeOnramp) {
stripeOnramp
.createSession({
clientSecret,
appearance,
})
.mount(containerRef);
}
}
}, [clientSecret, stripeOnramp]);

return <div {...props} ref={onrampElementRef}></div>;
};
57 changes: 57 additions & 0 deletions apps/platform/src/app/stripe/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client';

import { loadStripeOnramp } from '@stripe/crypto';
import {
CryptoElements,
OnrampElement,
} from '@/app/stripe/StripeCryptoElements';
import Container from '@/app/components/Container';
import React, { useEffect, useState } from 'react';

const stripeOnrampPromise = loadStripeOnramp(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || '',
);

export default function Stripe() {
// const clientSecret = process.env.STRIPE_SECRET_KEY || '';

const [clientSecret, setClientSecret] = useState('');
const [message, setMessage] = useState('');

useEffect(() => {
// Fetches an onramp session and captures the client secret
fetch(`/api/create-onramp-session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
transaction_details: {
destination_currency: 'usdc',
destination_exchange_amount: '0.0001',
destination_network: 'gnosis',
},
}),
})
.then(res => res.json())
.then(data => {
console.log('data', data);
setClientSecret(data.clientSecret);
});
}, []);

const onChange = React.useCallback(({ session }: any) => {
setMessage(`OnrampSession is now in ${session.status} state.`);
}, []);

return (
<section className='py-4 pb-32'>
<Container className='mx-auto mb-6 flex flex-wrap justify-between gap-1 px-5 py-5 lg:gap-10'>
<h1 className='w-full pb-4 pt-4 text-center text-[28px] font-normal leading-normal text-primaryBlack md:text-4xl lg:border-b lg:border-borderGrayLight lg:pb-10 lg:pt-10 lg:text-left'>
STRIPE TEST
</h1>
<CryptoElements stripeOnramp={stripeOnrampPromise}>
<OnrampElement clientSecret={clientSecret} appearance={{}} />
</CryptoElements>
</Container>
</section>
);
}
Loading