diff --git a/apps/platform/package.json b/apps/platform/package.json
index 2cbec09..3e1eebf 100644
--- a/apps/platform/package.json
+++ b/apps/platform/package.json
@@ -15,6 +15,8 @@
"@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",
@@ -22,6 +24,7 @@
"next-intl": "^3.17.2",
"react": "18",
"react-dom": "18",
+ "stripe": "^16.12.0",
"wagmi": "2.9.0",
"zod": "^3.23.8"
},
diff --git a/apps/platform/src/app/api/create-onramp-session/route.ts b/apps/platform/src/app/api/create-onramp-session/route.ts
new file mode 100644
index 0000000..3be9b90
--- /dev/null
+++ b/apps/platform/src/app/api/create-onramp-session/route.ts
@@ -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,
+ },
+ );
+}
diff --git a/apps/platform/src/app/stripe/StripeCryptoElements.tsx b/apps/platform/src/app/stripe/StripeCryptoElements.tsx
new file mode 100644
index 0000000..193816c
--- /dev/null
+++ b/apps/platform/src/app/stripe/StripeCryptoElements.tsx
@@ -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 (
+
+ {children}
+
+ );
+};
+
+// 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
;
+};
diff --git a/apps/platform/src/app/stripe/StripeCryptoElementsOLD.tsx b/apps/platform/src/app/stripe/StripeCryptoElementsOLD.tsx
new file mode 100644
index 0000000..6f2376e
--- /dev/null
+++ b/apps/platform/src/app/stripe/StripeCryptoElementsOLD.tsx
@@ -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 = ({
+ 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 (
+
+ {children}
+
+ );
+};
+
+// 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 = ({
+ clientSecret,
+ appearance,
+ ...props
+}) => {
+ const stripeOnramp = useStripeOnramp();
+ const onrampElementRef = React.useRef(null);
+
+ React.useEffect(() => {
+ const containerRef = onrampElementRef.current;
+ if (containerRef) {
+ containerRef.innerHTML = '';
+
+ if (clientSecret && stripeOnramp) {
+ stripeOnramp
+ .createSession({
+ clientSecret,
+ appearance,
+ })
+ .mount(containerRef);
+ }
+ }
+ }, [clientSecret, stripeOnramp]);
+
+ return ;
+};
diff --git a/apps/platform/src/app/stripe/page.tsx b/apps/platform/src/app/stripe/page.tsx
new file mode 100644
index 0000000..8bcb442
--- /dev/null
+++ b/apps/platform/src/app/stripe/page.tsx
@@ -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 (
+
+
+
+ STRIPE TEST
+
+
+
+
+
+
+ );
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5e377e1..bf010a9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -29,6 +29,12 @@ importers:
'@react-google-maps/api':
specifier: ^2.19.3
version: 2.19.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@stripe/crypto':
+ specifier: ^0.0.4
+ version: 0.0.4(@stripe/stripe-js@4.5.0)
+ '@stripe/stripe-js':
+ specifier: ^4.5.0
+ version: 4.5.0
axios:
specifier: ^1.7.4
version: 1.7.4
@@ -50,6 +56,9 @@ importers:
react-dom:
specifier: '18'
version: 18.3.1(react@18.3.1)
+ stripe:
+ specifier: ^16.12.0
+ version: 16.12.0
wagmi:
specifier: 2.9.0
version: 2.9.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.74.3(@babel/core@7.24.8)(@babel/preset-env@7.24.8(@babel/core@7.24.8))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1)))(@tanstack/query-core@5.51.1)(@tanstack/react-query@5.51.1(react@18.3.1))(@types/react@18.3.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.3(@babel/core@7.24.8)(@babel/preset-env@7.24.8(@babel/core@7.24.8))(@types/react@18.3.3)(bufferutil@4.0.8)(react@18.3.1))(react@18.3.1)(rollup@4.18.1)(typescript@5.5.3)(viem@2.17.4(bufferutil@4.0.8)(typescript@5.5.3)(zod@3.23.8))(zod@3.23.8)
@@ -2996,6 +3005,15 @@ packages:
'@stablelib/x25519@1.0.3':
resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==}
+ '@stripe/crypto@0.0.4':
+ resolution: {integrity: sha512-gcD/aG0N90ZrNVppWYf9ADPECptw6PVtF67VIeaFP7fhgd2NvNx8erkzlcvk3VIVSY+bZ6YGX7c7cASoySX74Q==}
+ peerDependencies:
+ '@stripe/stripe-js': ^1.46.0
+
+ '@stripe/stripe-js@4.5.0':
+ resolution: {integrity: sha512-dMOzc58AOlsF20nYM/avzV8RFhO/vgYTY7ajLMH6mjlnZysnOHZxsECQvjEmL8Q/ukPwHkOnxSPW/QGCCnp7XA==}
+ engines: {node: '>=12.16'}
+
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
@@ -7045,6 +7063,10 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
+ qs@6.13.0:
+ resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
+ engines: {node: '>=0.6'}
+
query-string@7.1.3:
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
engines: {node: '>=6'}
@@ -7771,6 +7793,10 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ stripe@16.12.0:
+ resolution: {integrity: sha512-H7eFVLDxeTNNSn4JTRfL2//LzCbDrMSZ+2q1c7CanVWgK2qIW5TwS+0V7N9KcKZZNpYh/uCqK0PyZh/2UsaAtQ==}
+ engines: {node: '>=12.*'}
+
strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
@@ -12208,6 +12234,12 @@ snapshots:
'@stablelib/random': 1.0.2
'@stablelib/wipe': 1.0.1
+ '@stripe/crypto@0.0.4(@stripe/stripe-js@4.5.0)':
+ dependencies:
+ '@stripe/stripe-js': 4.5.0
+
+ '@stripe/stripe-js@4.5.0': {}
+
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.12':
@@ -17475,6 +17507,10 @@ snapshots:
pngjs: 5.0.0
yargs: 15.4.1
+ qs@6.13.0:
+ dependencies:
+ side-channel: 1.0.6
+
query-string@7.1.3:
dependencies:
decode-uri-component: 0.2.2
@@ -18391,6 +18427,11 @@ snapshots:
strip-json-comments@3.1.1: {}
+ stripe@16.12.0:
+ dependencies:
+ '@types/node': 20.14.10
+ qs: 6.13.0
+
strnum@1.0.5: {}
style-to-object@1.0.6: