Quickstart
Learn how to build crypto payments directly into your product using Pluto.
Overview
You can build a custom payment flow using the Pluto React library, which allows you to accept multiple cryptocurrencies using a single integration. In this example, we'll set up a custom Ethereum payment flow using Next.js and the Pluto Node and React libraries. You can view the full source code here.
Set up Pluto
Use the official pluto-node
library to interact with the Pluto API from your application:
# Using NPM
npm install @plutohq/pluto-node --save
# Using Yarn
yarn add @plutohq/pluto-node
Create a payment
Using cryptocurrencies like Ethereum, Solana, and Bitcoin for payments is fundamentally different from processing credit card payments. With credit cards, merchants can charge customers automatically without client-side confirmation — the user submits their credit card information, and the site can charge the user's credit card at any time. Crypto, on the other hand, cannot be automatically withdrawn from a user's wallet; the customer needs to manually approve the transaction. Because all crypto payments begin as an intent to pay, they are referred to as a PaymentIntent, as opposed to a Charge, Payment, or Transaction.
When a user submits the checkout form, we'll submit their information to the /checkout
route, which will create a Customer and a PaymentIntent. Once the PaymentIntent is successfully created, we send the data back to the browser for confirmation.
import { Pluto } from '@plutohq/pluto-node';
// Initialize the Pluto Node.js library using your test secret key.
// See your keys here: https://pluto.co/developers
const pluto = new Pluto(process.env.PLUTO_SECRET_KEY);
async function handler(req, res) {
try {
if (req.method === 'POST') {
if (!req.body.name || !req.body.email) {
return res.status(400).json({ error: 'Missing name or email' });
}
const customer = await pluto.customers.create({
name: req.body.name,
email: req.body.email,
});
const paymentIntent = await pluto.paymentIntents.create({
chain: 'eth',
currency: 'eth',
amount: 0.01,
customer: customer.id,
});
return res.send(paymentIntent);
}
return res.status(400).json({ message: 'Invalid request' });
} catch (err) {
console.log(err);
return res.status(500).json({ error: err.message });
}
}
export default handler;
Collect payment details
We recommend using our React library, which includes components that allow you to collect a user's wallet information and hooks for confirming and polling PaymentIntents. You can also use our client-side JavaScript library if you want to implement this logic yourself.
# Using NPM
npm install @plutohq/pluto-react --save
# Using Yarn
yarn add @plutohq/pluto-react
To initialize the client library, wrap the checkout form in the Pluto provider context and pass your publishable API key.
import { loadPluto } from '@plutohq/pluto-js';
import { PlutoConfig } from '@plutohq/pluto-react';
import { NextSeo } from 'next-seo';
import React from 'react';
import CheckoutForm from '../components/CheckoutForm';
function Index() {
const pluto = loadPluto(process.env.NEXT_PUBLIC_PLUTO_PUBLISHABLE_TEST_KEY);
return (
<PlutoConfig pluto={pluto}>
<NextSeo title="Pluto Crypto Checkout Quickstart" noindex />
<CheckoutForm />
</PlutoConfig>
);
}
export default Index;
The client library offers a headless wallet connection component, which can be used for displaying supported wallets as well as connecting or disconnecting the user's wallet.
import { ConnectEthWallet as ConnectWallet } from '@plutohq/pluto-react';
import React from 'react';
export default function ConnectEthWallet() {
return (
<ConnectWallet>
{({ address, connectors, connect, disconnect }) => (
<div>
<div>
<label htmlFor="address">
{address ? 'Wallet address' : 'Connect a wallet'}
</label>
{address && (
<button
type="button"
onClick={() => disconnect()}
>
Disconnect
</button>
)}
</div>
{address ? (
<input
readOnly
type="text"
name="address"
value={address}
/>
) : (
<div>
{connectors.map((connector) => (
<button
key={connector.id}
type="button"
onClick={() => connect({ connector })}
>
{connector.name}
</button>
))}
</div>
)}
</div>
)}
</ConnectWallet>
);
}
Once the user has connected their wallet, we call the usePluto()
hook to return an instance of @plutohq/pluto-js
. This gives us access to the confirmPayment()
and waitForPayment()
methods, which will send the payment and poll the payment intent until it has either succeeded or failed.
import { usePluto } from '@plutohq/pluto-react';
import React from 'react';
import ConnectEthWallet from './ConnectEthWallet';
import PaymentModal from './PaymentModal';
export default function CheckoutForm() {
const pluto = usePluto();
const [loading, setLoading] = React.useState(false);
const [transaction, setTransaction] = React.useState(null);
const [showPaymentModal, setShowPaymentModal] = React.useState(false);
const handleSubmit = React.useCallback(
async (event) => {
event.preventDefault();
setLoading(true);
const paymentIntent = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: event.target.name.value,
email: event.target.email.value,
}),
})
.then((res) => res.json())
.then((result) => {
if (result.error) {
setLoading(false);
console.error(result.error);
return null;
}
return result;
});
if (paymentIntent) {
await pluto.confirmPayment(paymentIntent.id)
.then(async (data) => {
setTransaction(data.hash);
const completedPayment = await pluto.waitForPayment(paymentIntent.id);
if (completedPayment.status === 'succeeded') {
console.log('Payment intent succeeded!');
setLoading(false);
}
})
.catch(console.error)
.finally(() => setLoading(false));
}
},
[pluto],
);
return (
<form id="checkout-form" onSubmit={handleSubmit}>
<ConnectEthWallet />
<input type="text" name="name" />
<input type="text" name="email" />
<button type="submit">{loading ? 'Processing...' : 'Pay now'}</button>
{transaction && (
<div>
Your transaction hash:
{transaction}
</div>
)}
</form>
);
}
After the payment
If you want to execute any logic after the payment succeeds, you can use a webhook to listen for any updates to the payment. You can set up a webhook via the dashboard or the API. If you use the API, the signing secret is returned in the response, which is used for verifying the webhook in the next step.
const webhook = await pluto.webhooks.create({
endpoint_url: 'https://api.example.com/webhook',
event_types: ['*'],
});
Once you've added an endpoint to your server to receive webhooks, you're all set. You can view the full list of events here.
const { buffer } = require('micro');
const { Webhook } = require('svix');
const webhook = new Webhook(process.env.PLUTO_WEBHOOK_SECRET);
export default async function handler(req, res) {
try {
if (req.method === 'POST') {
const body = (await buffer(req))?.toString();
const event = webhook.verify(body, req.headers);
if (event.type === 'payment_intent.succeeded') {
const payment = event.data;
console.log(`Payment ${payment.id} for ${payment.amount} ${payment.currency.toUpperCase()} succeeded`);
}
if (event.type === 'payment_intent.failed') {
const payment = event.data;
console.log(`Payment ${payment.id} for ${payment.amount} ${payment.currency.toUpperCase()} failed`);
}
}
return res.send(202);
} catch (err) {
return res.status(500).json({ error: err.message });
}
}
export const config = {
api: {
bodyParser: false,
},
};
Test the integration
We recommend using a service such as Svix or ngrok to forward requests to your local API. You can test checkout flows using test mode.
Updated 6 months ago