Widget Integration Guide
With the SoloPay payment widget, you can integrate payments without building a custom payment UI. The SDK handles wallet connection, signing, and payment processing.
Choose the package that fits your framework.
React Projects
The @solo-pay/widget-react package integrates the widget using a React hook.
Installation
npm install @solo-pay/widget-reactUsage
import { useWidget } from '@solo-pay/widget-react';
function CheckoutButton({ orderId, amount }) {
const { openWidget } = useWidget({
publicKey: 'pk_xxxxx', // Your issued Public Key
defaultPaymentRequest: {
tokenAddress: '0xE4C687167705Abf55d709395f92e254bdF5825a2',
successUrl: 'https://myshop.com/payment/success',
failUrl: 'https://myshop.com/payment/fail',
currency: 'USD',
},
onClose: () => console.log('Widget closed.'),
onError: (err) => console.error('Payment error:', err),
});
return (
<button onClick={() => openWidget({ orderId, amount: String(amount) })}>
Pay Now
</button>
);
}useWidget initializes the SDK instance on mount and automatically cleans up on unmount.
Vanilla JS / Other Frameworks
The @solo-pay/widget-js package works without any framework.
Installation
npm install @solo-pay/widget-jsUsage
import { SoloPay } from '@solo-pay/widget-js';
const solopay = new SoloPay({
publicKey: 'pk_xxxxx',
});
solopay.requestPayment(
{
orderId: 'order-2024-00001',
amount: '25.5',
tokenAddress: '0xE4C687167705Abf55d709395f92e254bdF5825a2',
successUrl: 'https://myshop.com/payment/success',
failUrl: 'https://myshop.com/payment/fail',
currency: 'USD',
},
{
onClose: () => {
// Handle user closing the widget
},
}
);CDN
Use the widget directly via script tag without npm.
<script src="https://cdn.jsdelivr.net/npm/@solo-pay/widget-js/dist/widget.min.js"></script>
<script>
const solopay = new SoloPay({ publicKey: 'pk_xxxxx' });
solopay.requestPayment({
orderId: 'order-2024-00001',
amount: '25.5',
tokenAddress: '0xE4C687167705Abf55d709395f92e254bdF5825a2',
successUrl: 'https://myshop.com/payment/success',
failUrl: 'https://myshop.com/payment/fail',
currency: 'USD',
});
</script>amount and currency Behavior
How amount is interpreted depends on whether currency is provided.
currency | amount interpretation |
|---|---|
Provided (e.g., 'USD', 'KRW') | Fiat amount — automatically converted using the latest 1-hour average price (TWAP) |
| Omitted | Token amount directly — used as-is, no conversion |
Example 1: USD-based payment (currency provided)
// amount: 25.5 USD → converted to token amount at the latest 1-hour average price
solopay.requestPayment({
orderId: 'order-001',
amount: '25.5',
currency: 'USD',
tokenAddress: '0xE4C687167705Abf55d709395f92e254bdF5825a2',
successUrl: 'https://myshop.com/payment/success',
failUrl: 'https://myshop.com/payment/fail',
});Example 2: Token amount directly (currency omitted)
// amount: 25.5 USDT directly (no conversion)
solopay.requestPayment({
orderId: 'order-001',
amount: '25.5',
// currency omitted → amount is used as token amount directly
tokenAddress: '0xE4C687167705Abf55d709395f92e254bdF5825a2',
successUrl: 'https://myshop.com/payment/success',
failUrl: 'https://myshop.com/payment/fail',
});What happens when currency is omitted?
When currency is omitted, amount is treated as the token amount directly. For example, passing amount: '25.5' with a USDT token requests exactly 25.5 USDT. Use this when no currency conversion is needed.
How It Works
- Desktop: The widget opens as a popup window.
- Mobile: The user is redirected to a full-screen page.
- After payment completion or failure, the widget auto-redirects to
successUrlorfailUrl.
Callback URL Handling
After payment, SoloPay redirects the user to the successUrl or failUrl specified at payment creation. The widget automatically appends paymentId, orderId, and status as query parameters.
| Parameter | Description |
|---|---|
paymentId | The unique payment identifier |
orderId | The merchant order ID |
status | Payment result: success, fail, or closed |
https://myshop.com/payment/success?paymentId=0xabc123...&orderId=order-001&status=success
https://myshop.com/payment/fail?paymentId=0xabc123...&orderId=order-001&status=fail
https://myshop.com/payment/fail?paymentId=0xabc123...&orderId=order-001&status=closedDo Not Trust URL Parameters
URL parameters can be manipulated by the user. Always verify payment status via API as the final check.
Payment Verification (Required)
As soon as the paymentId is received from the callback URL, call the status API to verify payment. The GET /payments/:id endpoint uses the x-public-key header, which can be called from the browser.
const response = await fetch(`https://gateway.dev.solonetwork.io/api/v1/payments/0xabc123...`, {
headers: { 'x-public-key': 'pk_xxxxx' },
});
const result = await response.json();Common Verification Checklist
- [ ] Confirm
status === 'PAID'(payment success) - [ ] Confirm
tokenAddressmatches the expected token - [ ] Confirm
orderIdmatches the expected orderId - [ ] Prevent duplicate completion processing for the same
paymentId
Amount verification — what to compare depends on whether currency was used when creating the payment.
The widget runs client-side, so the amount can be tampered with. Always compare against the expected amount stored in your order database.
When the payment was created with currency (fiat-based):
- [ ]
currencymatches the currency code stored with the order - [ ]
fiatAmountmatches the fiat amount stored with the order - [ ] (Optional)
tokenPriceis within an acceptable range — guards against price swings
When the payment was created without currency (direct token amount):
- [ ]
amount(wei) matches the expected token amount in wei — the responseamountis always in wei, so convert your expected value withparseUnits(expected, tokenDecimals)before comparing.
amount is always in wei
The response amount field is always in wei, regardless of whether currency was used. When currency is provided, the user's fiat input is stored in fiatAmount, while amount holds the converted wei value. Do not compare amount directly against a fiat value.
Webhook Integration Recommended
Callbacks are browser-redirect based and can be lost due to network issues. Using it with Webhooks allows reliable payment completion reception. View Webhook Setup Guide
Approve-Only Mode
The widget supports an approve-only mode that performs only the ERC-20 token approval step without executing a payment. This is useful for:
- Pre-authorizing token spending before the actual payment
- Faster checkout — users approve once, then future payments skip the approval step
- Recurring payments — approve once with a high allowance, pay multiple times
React
import { useWidget } from '@solo-pay/widget-react';
function PreApproveButton() {
const { openApproval } = useWidget({
publicKey: 'pk_xxxxx',
onClose: () => console.log('Widget closed.'),
onError: (err) => console.error('Error:', err),
});
return (
<button onClick={() => openApproval({
tokenAddress: '0xE4C687167705Abf55d709395f92e254bdF5825a2',
successUrl: 'https://myshop.com/approve/success',
failUrl: 'https://myshop.com/approve/fail',
})}>
Pre-Approve Token
</button>
);
}Vanilla JS
const solopay = new SoloPay({ publicKey: 'pk_xxxxx' });
solopay.requestApproval({
tokenAddress: '0xE4C687167705Abf55d709395f92e254bdF5825a2',
successUrl: 'https://myshop.com/approve/success',
failUrl: 'https://myshop.com/approve/fail',
});Callback URL Parameters (Approve-Only)
After approval, the widget redirects to successUrl or failUrl with these query parameters:
| Parameter | Description |
|---|---|
status | Result: approved or closed |
tokenAddress | The token contract address that was approved |
txHash | Approval transaction hash (only on success) |
https://myshop.com/approve/success?status=approved&tokenAddress=0x...&txHash=0x...
https://myshop.com/approve/fail?status=closed&tokenAddress=0x...Next Steps
- Webhook Setup — Reliable payment completion notifications