SolRUO RETURN
DEVELOPER GUIDE — v1.0

Integrating SolRUO compliant payments

This guide walks your engineering team through wiring SolRUO into a research-use-only distributor checkout. Onboarding to the SolRUO Stripe platform is handled separately by our compliance team — once complete, you will receive the credentials referenced below.

STACK
Any
FRONTEND
Stripe.js
BACKEND
1 endpoint
TIME
~30 min

What SolRUO provides you

After your brand is approved and onboarded to the SolRUO Stripe organization, our team will send you the following credentials. Treat the secret values as you would any production secret.

CredentialWhere it livesExample
CLIENT_SLUGServer env varyour-brand-slug
CLIENT_ACCOUNT_IDServer env varacct_1XXXXXXXXX
STRIPE_PUBLISHABLE_KEYClient (safe to expose)pk_live_XXXX
Important
Never put your CLIENT_ACCOUNT_ID directly in browser code. The browser only ever sees the Stripe clientSecret returned from your own backend.

How the flow works

  1. 1. Customer clicks Checkout on your site.
  2. 2. Your backend calls POST /api/public/create-payment on SolRUO with the order amount and your credentials.
  3. 3. SolRUO creates a Stripe PaymentIntent on the platform, routes funds to your connected account, deducts the SolRUO platform fee, and returns a clientSecret.
  4. 4. Your frontend mounts Stripe Elements with that secret and confirms the payment client-side.
  5. 5. Stripe webhooks fire to your own account so you can fulfill the order.

API reference

One endpoint. CORS is enabled, but you should always call it server-to-server so your credentials never reach the browser.

http
POST https://solruo.com/api/public/create-payment
Content-Type: application/json

{
  "amount": 9900,                  // integer, cents (USD), required
  "currency": "usd",               // optional, defaults to "usd"
  "clientSlug": "your-brand-slug", // provided by SolRUO
  "clientAccountId": "acct_...",  // provided by SolRUO
  "metadata": { "order_id": "123" } // optional, string values only
}

→ 200 OK
{
  "clientSecret": "pi_3..._secret_...",
  "paymentIntentId": "pi_3...",
  "applicationFeeAmount": 1144
}
STEP 01

Add your environment variables

Drop these into .env.local (Next.js), .env (Node/Express), or your hosting provider's secret store.

.env.localenv
# .env.local — provided by SolRUO during onboarding
SOLRUO_API_URL=https://solruo.com/api/public/create-payment
SOLRUO_CLIENT_SLUG=your-brand-slug
SOLRUO_CLIENT_ACCOUNT_ID=acct_XXXXXXXXXXXXXXXX

# Stripe — platform publishable key (safe to expose in client code)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_XXXXXXXXXXXXXXXXXXXXXXXX
STEP 02

Install Stripe on the frontend

bash
npm install @stripe/stripe-js @stripe/react-stripe-js
# or
pnpm add @stripe/stripe-js @stripe/react-stripe-js
STEP 03

Add the backend checkout route

This is the only piece of server code you need. Pick the snippet for your stack.

app/api/checkout/route.ts (Next.js)ts
// app/api/checkout/route.ts  (Next.js App Router)
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const { amount } = await req.json(); // amount in cents

  if (!Number.isInteger(amount) || amount < 100) {
    return NextResponse.json({ error: "Invalid amount" }, { status: 400 });
  }

  const res = await fetch(process.env.SOLRUO_API_URL!, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      amount,
      currency: "usd",
      clientSlug: process.env.SOLRUO_CLIENT_SLUG,
      clientAccountId: process.env.SOLRUO_CLIENT_ACCOUNT_ID,
    }),
  });

  const data = await res.json();
  if (!res.ok) {
    return NextResponse.json({ error: data.error ?? "Payment failed" }, { status: 502 });
  }

  // Return only the clientSecret to the browser
  return NextResponse.json({ clientSecret: data.clientSecret });
}
server.js (Express)js
// server.js  (Node + Express)
import express from "express";
const app = express();
app.use(express.json());

app.post("/api/checkout", async (req, res) => {
  const { amount } = req.body; // cents
  if (!Number.isInteger(amount) || amount < 100) {
    return res.status(400).json({ error: "Invalid amount" });
  }

  const r = await fetch(process.env.SOLRUO_API_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      amount,
      currency: "usd",
      clientSlug: process.env.SOLRUO_CLIENT_SLUG,
      clientAccountId: process.env.SOLRUO_CLIENT_ACCOUNT_ID,
    }),
  });

  const data = await r.json();
  if (!r.ok) return res.status(502).json({ error: data.error });
  res.json({ clientSecret: data.clientSecret });
});

app.listen(3000);
STEP 04

Mount the checkout component

A drop-in React component using Stripe's PaymentElement. Works in any React app — Vite, Next.js, Remix, etc.

components/SolruoCheckout.tsxtsx
// components/SolruoCheckout.tsx
import { useEffect, useState } from "react";
import { loadStripe } from "@stripe/stripe-js";
import {
  Elements,
  PaymentElement,
  useStripe,
  useElements,
} from "@stripe/react-stripe-js";

const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!);
// In Next.js use: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!

export function SolruoCheckout({ amountInCents }: { amountInCents: number }) {
  const [clientSecret, setClientSecret] = useState<string | null>(null);

  useEffect(() => {
    fetch("/api/checkout", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ amount: amountInCents }),
    })
      .then((r) => r.json())
      .then((d) => setClientSecret(d.clientSecret));
  }, [amountInCents]);

  if (!clientSecret) return <div>Loading secure checkout…</div>;

  return (
    <Elements stripe={stripePromise} options={{ clientSecret, appearance: { theme: "night" } }}>
      <CheckoutForm />
    </Elements>
  );
}

function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;
    setSubmitting(true);
    setError(null);

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: { return_url: `${window.location.origin}/order/success` },
    });

    if (error) setError(error.message ?? "Payment failed");
    setSubmitting(false);
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <PaymentElement />
      {error && <div className="text-red-500 text-sm">{error}</div>}
      <button
        disabled={!stripe || submitting}
        className="w-full rounded-md bg-black px-4 py-3 text-white"
      >
        {submitting ? "Processing…" : "Pay now"}
      </button>
    </form>
  );
}

Then use it from any page:

tsx
<SolruoCheckout amountInCents={9900} />
STEP 05

Or use plain HTML / no framework

checkout.htmlhtml
<!-- Plain HTML / vanilla JS checkout -->
<!doctype html>
<html>
  <body>
    <form id="payment-form">
      <div id="payment-element"></div>
      <button id="submit">Pay</button>
      <div id="error" style="color:red"></div>
    </form>

    <script src="https://js.stripe.com/v3/"></script>
    <script type="module">
      const stripe = Stripe("pk_live_XXXXXXXXXXXXXXXXXXXX"); // platform publishable key

      const res = await fetch("/api/checkout", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ amount: 9900 }), // $99.00
      });
      const { clientSecret } = await res.json();

      const elements = stripe.elements({ clientSecret });
      elements.create("payment").mount("#payment-element");

      document.getElementById("payment-form").addEventListener("submit", async (e) => {
        e.preventDefault();
        const { error } = await stripe.confirmPayment({
          elements,
          confirmParams: { return_url: window.location.origin + "/success" },
        });
        if (error) document.getElementById("error").textContent = error.message;
      });
    </script>
  </body>
</html>
STEP 06

Handle the success redirect

After the customer confirms payment Stripe redirects to your return_url with the PaymentIntent ID attached.

app/order/success/page.tsxtsx
// app/order/success/page.tsx — verify the payment server-side
export default async function SuccessPage({
  searchParams,
}: {
  searchParams: { payment_intent?: string; payment_intent_client_secret?: string };
}) {
  const piId = searchParams.payment_intent;
  // Optional: look up the PaymentIntent via your own backend
  // before marking the order as paid. Stripe will also send
  // a webhook (payment_intent.succeeded) which is the most reliable signal.
  return (
    <main className="p-10">
      <h1 className="text-2xl">Order received</h1>
      <p>Reference: {piId}</p>
    </main>
  );
}
STEP 07

Listen for webhooks (recommended)

Webhooks are the only reliable way to know an order was paid. Configure a webhook endpoint inside your own Stripe Connect account dashboard pointing at the route below, and subscribe to payment_intent.succeeded.

app/api/stripe/webhook/route.tsts
// app/api/stripe/webhook/route.ts
import Stripe from "stripe";
import { NextResponse } from "next/server";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); // your OWN account's secret key

export async function POST(req: Request) {
  const sig = req.headers.get("stripe-signature")!;
  const body = await req.text();

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!,
    );
  } catch (err) {
    return new NextResponse("Bad signature", { status: 400 });
  }

  if (event.type === "payment_intent.succeeded") {
    const pi = event.data.object as Stripe.PaymentIntent;
    // mark order as paid, fulfill, send receipt, etc.
    console.log("Paid:", pi.id, pi.amount);
  }

  return NextResponse.json({ received: true });
}
STEP 08

WooCommerce stores

The official WooCommerce Stripe plugin does not support Stripe Connect destination charges, so it cannot route funds into your SolRUO connected account. Use a custom SolRUO gateway plugin instead.

We provide a starter plugin (solruo-gateway.php) that adds a SolRUO payment method, creates a PaymentIntent via the SolRUO API, hosts a Stripe Elements payment page on your site, and listens for Stripe webhooks.

Set the webhook endpoint in your Stripe dashboard to:

text
https://your-site.com/wc-api/solruo_webhook

Place the plugin file in your WordPress install and enter the SolRUO credentials on WooCommerce → Settings → Payments → SolRUO.

Developer template
This is a starter template for a WordPress developer to review and deploy. Test on a staging site before going live.
STEP 09

Test, then go live

  • While SolRUO has your account in test mode, use Stripe's test card 4242 4242 4242 4242, any future expiry, any CVC, any ZIP.
  • When SolRUO flips your account to live, swap pk_test_… for pk_live_… on the frontend. No backend changes needed.
  • Confirm an end-to-end $1.00 charge in production before announcing checkout.
Fee breakdown
Every successful charge automatically deducts the SolRUO platform fee (currently 6.5% + $0.50, which covers Stripe processing). The remainder lands in your connected account on Stripe's standard payout schedule.

Need help?

Email support@solruo.com with your CLIENT_SLUG and we will respond within one business day. For Stripe-specific questions (disputes, payouts, tax) you can also contact Stripe support directly from your connected-account dashboard.