Actions
- @ductape/client
- React
- Vue 3
Execute Ductape Actions - pre-built integrations with external services like Stripe, Paystack, and more.
When using a publishable key (frontend), include session (token from your backend) in every request.
Basic Action Execution
import { Ductape } from '@ductape/client';
const ductape = new Ductape({
publishableKey: 'your-publishable-key',
product: 'your-product',
env: 'prd'
});
const sessionToken = getSessionFromYourBackend();
// Execute an action
const result = await ductape.api.run({
app: 'ductape:stripe',
action: 'create-customer',
input: {
email: 'customer@example.com',
name: 'John Doe'
},
session: sessionToken,
});
console.log('Customer ID:', result.id);
Payment Actions
Stripe Payment Intent
// Create Stripe payment intent
const paymentIntent = await ductape.api.execute({
action: 'stripe.create-payment-intent',
input: {
amount: 5000, // $50.00 in cents
currency: 'usd',
customer: 'cus_abc123',
automatic_payment_methods: {
enabled: true
}
}
});
console.log('Client Secret:', paymentIntent.client_secret);
Paystack Payment
// Initialize Paystack transaction
const transaction = await ductape.api.execute({
action: 'paystack.initialize-transaction',
input: {
email: 'customer@example.com',
amount: 50000, // 500 NGN in kobo
currency: 'NGN',
callback_url: 'https://yourapp.com/payment/callback'
}
});
// Redirect user to payment page
window.location.href = transaction.data.authorization_url;
Verify Paystack Payment
// Verify transaction after callback
const verification = await ductape.api.execute({
action: 'paystack.verify-transaction',
input: {
reference: 'transaction-reference'
}
});
if (verification.data.status === 'success') {
console.log('Payment successful!');
console.log('Amount:', verification.data.amount / 100);
console.log('Customer:', verification.data.customer.email);
}
Subscription Management
Create Stripe Subscription
const subscription = await ductape.api.execute({
action: 'stripe.create-subscription',
input: {
customer: 'cus_abc123',
items: [
{ price: 'price_monthly_plan' }
],
payment_behavior: 'default_incomplete',
expand: ['latest_invoice.payment_intent']
}
});
console.log('Subscription ID:', subscription.id);
console.log('Status:', subscription.status);
Create Paystack Plan
const plan = await ductape.api.execute({
action: 'paystack.create-plan',
input: {
name: 'Monthly Subscription',
amount: 500000, // 5000 NGN in kobo
interval: 'monthly',
description: 'Premium monthly subscription'
}
});
console.log('Plan Code:', plan.data.plan_code);
Customer Management
List Stripe Customers
const customers = await ductape.api.execute({
action: 'stripe.list-customers',
input: {
limit: 10
}
});
customers.data.forEach(customer => {
console.log(customer.email, customer.name);
});
List Paystack Customers
const customers = await ductape.api.execute({
action: 'paystack.list-customers',
input: {
perPage: 20,
page: 1
}
});
customers.data.forEach(customer => {
console.log(customer.email, customer.customer_code);
});
Transaction History
List Paystack Transactions
const transactions = await ductape.api.execute({
action: 'paystack.list-transactions',
input: {
perPage: 50,
status: 'success'
}
});
transactions.data.forEach(tx => {
console.log(`${tx.customer.email}: ${tx.currency} ${tx.amount / 100}`);
});
Retrieve Stripe Balance
const balance = await ductape.api.execute({
action: 'stripe.retrieve-balance',
input: {}
});
balance.available.forEach(bal => {
console.log(`${bal.currency.toUpperCase()}: ${bal.amount / 100}`);
});
balance.pending.forEach(bal => {
console.log(`Pending ${bal.currency.toUpperCase()}: ${bal.amount / 100}`);
});
Refunds
Create Stripe Refund
const refund = await ductape.api.execute({
action: 'stripe.create-refund',
input: {
payment_intent: 'pi_abc123',
amount: 2500, // Partial refund of $25
reason: 'requested_by_customer'
}
});
console.log('Refund Status:', refund.status);
Paystack Refund
const refund = await ductape.api.execute({
action: 'paystack.refund-transaction',
input: {
transaction: 'transaction-id',
amount: 25000 // Amount in kobo
}
});
console.log('Refund Status:', refund.data.status);
Action Chaining
Execute multiple actions in sequence:
async function processOrder(orderData: any) {
try {
// Create customer
const customer = await ductape.api.execute({
action: 'stripe.create-customer',
input: {
email: orderData.email,
name: orderData.name
}
});
// Create payment intent
const payment = await ductape.api.execute({
action: 'stripe.create-payment-intent',
input: {
amount: orderData.total * 100,
currency: 'usd',
customer: customer.id,
metadata: {
orderId: orderData.id
}
}
});
// Send confirmation
await ductape.notifications.send({
channel: 'email',
to: orderData.email,
template: 'order-confirmation',
data: {
orderNumber: orderData.id,
total: orderData.total
}
});
return payment;
} catch (error) {
console.error('Order processing failed:', error);
throw error;
}
}
Error Handling
try {
const result = await ductape.api.execute({
action: 'stripe.create-payment-intent',
input: {
amount: 5000,
currency: 'usd'
}
});
} catch (error) {
if (error.type === 'StripeCardError') {
console.error('Card was declined');
} else if (error.type === 'StripeInvalidRequestError') {
console.error('Invalid parameters:', error.message);
} else if (error.type === 'StripeAPIError') {
console.error('Stripe API error');
} else if (error.type === 'StripeConnectionError') {
console.error('Network error');
} else {
console.error('Unknown error:', error);
}
}
Retry Logic
async function executeActionWithRetry(
actionConfig: any,
maxRetries = 3,
delay = 1000
) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await ductape.api.execute(actionConfig);
} catch (error) {
lastError = error;
if (attempt < maxRetries) {
console.log(`Attempt ${attempt} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, delay * attempt));
}
}
}
throw lastError;
}
// Usage
const result = await executeActionWithRetry({
action: 'paystack.verify-transaction',
input: { reference: 'ref-123' }
});
Complete Payment Flow Example
class PaymentService {
private ductape: Ductape;
constructor() {
this.ductape = new Ductape({
accessKey: process.env.DUCTAPE_ACCESS_KEY!,
product: 'my-shop',
env: 'prd'
});
}
async createStripePayment(amount: number, email: string) {
// Create or retrieve customer
const customer = await this.ductape.api.execute({
action: 'stripe.create-customer',
input: { email }
});
// Create payment intent
const paymentIntent = await this.ductape.api.execute({
action: 'stripe.create-payment-intent',
input: {
amount: amount * 100,
currency: 'usd',
customer: customer.id,
automatic_payment_methods: { enabled: true }
}
});
return {
clientSecret: paymentIntent.client_secret,
customerId: customer.id,
paymentIntentId: paymentIntent.id
};
}
async createPaystackPayment(amount: number, email: string) {
const transaction = await this.ductape.api.execute({
action: 'paystack.initialize-transaction',
input: {
email,
amount: amount * 100, // Convert to kobo
currency: 'NGN',
callback_url: window.location.origin + '/verify'
}
});
return {
authorizationUrl: transaction.data.authorization_url,
reference: transaction.data.reference,
accessCode: transaction.data.access_code
};
}
async verifyPaystackPayment(reference: string) {
const verification = await this.ductape.api.execute({
action: 'paystack.verify-transaction',
input: { reference }
});
if (verification.data.status === 'success') {
return {
success: true,
amount: verification.data.amount / 100,
customer: verification.data.customer.email,
paidAt: verification.data.paid_at
};
}
return { success: false };
}
}
// Usage
const payments = new PaymentService();
// Stripe flow
const stripePayment = await payments.createStripePayment(99.99, 'user@example.com');
console.log('Client Secret:', stripePayment.clientSecret);
// Paystack flow
const paystackPayment = await payments.createPaystackPayment(5000, 'user@example.com');
window.location.href = paystackPayment.authorizationUrl;
// Later, verify Paystack payment
const verified = await payments.verifyPaystackPayment('reference-123');
if (verified.success) {
console.log('Payment verified!');
}
Next Steps
React hooks for executing Ductape Actions - pre-built integrations with external services.
When using a publishable key, every action call must include session in the payload. Get the session token from your backend (or from config, e.g. import { session } from './config'). The examples below include session in each mutate() call.
useActionRun
Execute any Ductape action with automatic loading states and error handling. With a publishable key, pass app, action, input, and session.
Basic Action Execution
import { useActionRun } from '@ductape/react';
import { session } from './config';
function CreateStripeCustomer() {
const { mutate, isLoading, error, data } = useActionRun({
onSuccess: (result) => {
console.log('Customer created:', result);
alert('Customer created successfully!');
}
});
const handleCreate = () => {
mutate({
app: 'ductape:stripe',
action: 'create-customer',
input: {
email: 'customer@example.com',
name: 'John Doe',
metadata: {
userId: 'user-123'
}
},
session,
});
};
return (
<div>
<button onClick={handleCreate} disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create Customer'}
</button>
{error && <p className="error">{error.message}</p>}
{data && <p>Customer ID: {data.customerId}</p>}
</div>
);
}
Action with Parameters
import { useActionRun } from '@ductape/react';
import { session } from './config';
function PaystackPaymentForm() {
const [amount, setAmount] = useState('');
const [email, setEmail] = useState('');
const [currency, setCurrency] = useState('NGN');
const { mutate: initializePayment, isLoading, data } = useActionRun({
onSuccess: (result) => {
console.log('Payment initialized:', result);
const res = result as { data?: { authorization_url?: string } };
if (res.data?.authorization_url) {
window.location.href = res.data.authorization_url;
}
}
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
initializePayment({
app: 'ductape:paystack',
action: 'initialize-transaction',
input: {
email,
amount: parseFloat(amount) * 100, // Convert to kobo/cents
currency,
callback_url: window.location.origin + '/payment/callback'
},
session,
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
required
/>
<select value={currency} onChange={(e) => setCurrency(e.target.value)}>
<option value="NGN">NGN</option>
<option value="USD">USD</option>
<option value="GHS">GHS</option>
</select>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Processing...' : 'Pay with Paystack'}
</button>
</form>
);
}
useActionQuery
Query data from an action (for read operations). Include session in options when using a publishable key.
import { useActionQuery } from '@ductape/react';
import { session } from './config';
function PaystackTransactions() {
const { data, isLoading, error } = useActionQuery(
'paystack-transactions',
{
app: 'ductape:paystack',
action: 'list-transactions',
input: {
perPage: 50,
status: 'success'
},
session,
}
);
if (isLoading) return <div>Loading transactions...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data?.transactions.map((transaction: any) => (
<li key={transaction.id}>
<span>{transaction.customer.email}</span>
<span>{transaction.currency} {transaction.amount / 100}</span>
<span>{new Date(transaction.created_at).toLocaleDateString()}</span>
</li>
))}
</ul>
);
}
Query with Filters
import { session } from './config';
function StripeCustomers() {
const [limit, setLimit] = useState(10);
const { data, isLoading } = useActionQuery(
['stripe-customers', limit],
{
app: 'ductape:stripe',
action: 'list-customers',
input: { limit },
session,
}
);
return (
<div>
<select value={limit} onChange={(e) => setLimit(Number(e.target.value))}>
<option value={10}>10</option>
<option value={25}>25</option>
<option value={50}>50</option>
<option value={100}>100</option>
</select>
{isLoading ? (
<div>Loading...</div>
) : (
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Created</th>
</tr>
</thead>
<tbody>
{data?.data.map((customer: any) => (
<tr key={customer.id}>
<td>{customer.name}</td>
<td>{customer.email}</td>
<td>{new Date(customer.created * 1000).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
Common Action Examples
Create Stripe Payment Intent
function StripePaymentForm({ amount, customerId }: { amount: number; customerId: string }) {
const { mutate: createPayment, isLoading, data } = useAction({
onSuccess: (result) => {
console.log('Payment intent created:', result.id);
}
});
const handlePayment = () => {
createPayment({
action: 'stripe.create-payment-intent',
input: {
amount: amount * 100, // Convert to cents
currency: 'usd',
customer: customerId,
automatic_payment_methods: {
enabled: true
}
}
});
};
return (
<div>
<p>Amount: ${amount}</p>
<button onClick={handlePayment} disabled={isLoading}>
{isLoading ? 'Processing...' : 'Pay Now'}
</button>
{data && (
<div>
<p>Payment Intent ID: {data.id}</p>
<p>Status: {data.status}</p>
<p>Client Secret: {data.client_secret}</p>
</div>
)}
</div>
);
}
Verify Paystack Transaction
import { session } from './config';
function PaystackVerification({ reference }: { reference: string }) {
const { data, isLoading, error } = useActionQuery(
['paystack-verify', reference],
{
app: 'ductape:paystack',
action: 'verify-transaction',
input: { reference },
session,
}
);
if (isLoading) return <div>Verifying payment...</div>;
if (error) return <div>Verification failed: {error.message}</div>;
return (
<div className="verification-result">
{data?.data.status === 'success' ? (
<div className="success">
<h3>Payment Successful!</h3>
<p>Amount: {data.data.currency} {data.data.amount / 100}</p>
<p>Reference: {data.data.reference}</p>
<p>Paid by: {data.data.customer.email}</p>
</div>
) : (
<div className="error">
<h3>Payment Failed</h3>
<p>Status: {data?.data.status}</p>
</div>
)}
</div>
);
}
Create Stripe Subscription
import { useActionRun } from '@ductape/react';
import { session } from './config';
function SubscriptionForm({ customerId, priceId }: { customerId: string; priceId: string }) {
const { mutate: createSubscription, isLoading, data } = useActionRun({
onSuccess: (result) => {
alert('Subscription created!');
console.log('Subscription:', result);
}
});
const handleSubscribe = () => {
createSubscription({
app: 'ductape:stripe',
action: 'create-subscription',
input: {
customer: customerId,
items: [{ price: priceId }],
payment_behavior: 'default_incomplete',
payment_settings: {
payment_method_types: ['card']
},
expand: ['latest_invoice.payment_intent']
},
session,
});
};
return (
<div>
<button onClick={handleSubscribe} disabled={isLoading}>
{isLoading ? 'Creating Subscription...' : 'Subscribe'}
</button>
{data && (
<div>
<p>Subscription ID: {data.id}</p>
<p>Status: {data.status}</p>
</div>
)}
</div>
);
}
List Paystack Customers
import { session } from './config';
function PaystackCustomersList() {
const [page, setPage] = useState(1);
const { data, isLoading } = useActionQuery(
['paystack-customers', page],
{
app: 'ductape:paystack',
action: 'list-customers',
input: { perPage: 20, page },
session,
}
);
if (isLoading) return <div>Loading customers...</div>;
return (
<div>
<table>
<thead>
<tr>
<th>Email</th>
<th>First Name</th>
<th>Last Name</th>
<th>Customer Code</th>
</tr>
</thead>
<tbody>
{data?.data.map((customer: any) => (
<tr key={customer.id}>
<td>{customer.email}</td>
<td>{customer.first_name}</td>
<td>{customer.last_name}</td>
<td>{customer.customer_code}</td>
</tr>
))}
</tbody>
</table>
<div className="pagination">
<button
onClick={() => setPage(p => Math.max(1, p - 1))}
disabled={page === 1}
>
Previous
</button>
<span>Page {page}</span>
<button onClick={() => setPage(p => p + 1)}>
Next
</button>
</div>
</div>
);
}
Retrieve Stripe Balance
import { session } from './config';
function StripeBalanceDisplay() {
const { data, isLoading, refetch } = useActionQuery(
'stripe-balance',
{
app: 'ductape:stripe',
action: 'retrieve-balance',
input: {},
session,
}
);
if (isLoading) return <div>Loading balance...</div>;
return (
<div className="balance-card">
<h3>Account Balance</h3>
<button onClick={() => refetch()}>Refresh</button>
<div className="available">
<h4>Available</h4>
{data?.available.map((balance: any, idx: number) => (
<p key={idx}>
{balance.currency.toUpperCase()}: {balance.amount / 100}
</p>
))}
</div>
<div className="pending">
<h4>Pending</h4>
{data?.pending.map((balance: any, idx: number) => (
<p key={idx}>
{balance.currency.toUpperCase()}: {balance.amount / 100}
</p>
))}
</div>
</div>
);
}
Paystack Create Plan
import { useActionRun } from '@ductape/react';
import { session } from './config';
function CreatePaystackPlan() {
const [planData, setPlanData] = useState({
name: '',
amount: '',
interval: 'monthly'
});
const { mutate, isLoading } = useActionRun({
onSuccess: (result) => {
alert('Plan created successfully!');
console.log('Plan:', result);
}
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({
app: 'ductape:paystack',
action: 'create-plan',
input: {
name: planData.name,
amount: parseFloat(planData.amount) * 100, // Convert to kobo
interval: planData.interval
},
session,
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={planData.name}
onChange={(e) => setPlanData({ ...planData, name: e.target.value })}
placeholder="Plan Name"
required
/>
<input
type="number"
value={planData.amount}
onChange={(e) => setPlanData({ ...planData, amount: e.target.value })}
placeholder="Amount"
required
/>
<select
value={planData.interval}
onChange={(e) => setPlanData({ ...planData, interval: e.target.value })}
>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
<option value="annually">Annually</option>
</select>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create Plan'}
</button>
</form>
);
}
Action Chaining
Execute multiple actions in sequence. Include session in each call when using a publishable key.
import { useActionRun } from '@ductape/react';
import { session } from './config';
function PaymentProcessor({ email, amount }: { email: string; amount: number }) {
const { mutateAsync: createCustomer } = useActionRun();
const { mutateAsync: createPayment } = useActionRun();
const { mutateAsync: sendReceipt } = useActionRun();
const processPayment = async () => {
try {
const customer = await createCustomer({
app: 'ductape:stripe',
action: 'create-customer',
input: { email, metadata: { source: 'web-checkout' } },
session,
});
const payment = await createPayment({
app: 'ductape:stripe',
action: 'create-payment-intent',
input: {
amount: amount * 100,
currency: 'usd',
customer: (customer as any).id,
automatic_payment_methods: { enabled: true }
},
session,
});
await sendReceipt({
app: 'ductape:notifications',
action: 'send',
input: {
channel: 'email',
to: email,
template: 'payment-receipt',
data: { amount, paymentId: (payment as any).id, customerName: email }
},
session,
});
alert('Payment processed successfully!');
return payment;
} catch (error) {
console.error('Payment processing failed:', error);
throw error;
}
};
return (
<button onClick={processPayment}>
Process Payment
</button>
);
}
Paystack Payment Flow with Verification
import { useActionRun } from '@ductape/react';
import { session } from './config';
function PaystackPaymentFlow({ email, amount }: { email: string; amount: number }) {
const [reference, setReference] = useState<string | null>(null);
const { mutateAsync: initialize } = useActionRun();
const { mutateAsync: verify } = useActionRun();
const startPayment = async () => {
try {
const transaction = await initialize({
app: 'ductape:paystack',
action: 'initialize-transaction',
input: {
email,
amount: amount * 100,
currency: 'NGN',
callback_url: window.location.origin + '/verify'
},
session,
}) as any;
setReference(transaction?.data?.reference);
if (transaction?.data?.authorization_url) {
window.location.href = transaction.data.authorization_url;
}
} catch (error) {
console.error('Payment initialization failed:', error);
}
};
const verifyPayment = async (ref: string) => {
try {
const verification = await verify({
app: 'ductape:paystack',
action: 'verify-transaction',
input: { reference: ref },
session,
}) as any;
if (verification?.data?.status === 'success') {
alert('Payment verified successfully!');
} else {
alert('Payment verification failed');
}
} catch (error) {
console.error('Verification failed:', error);
}
};
return (
<div>
<button onClick={startPayment}>
Pay with Paystack
</button>
{reference && (
<button onClick={() => verifyPayment(reference)}>
Verify Payment
</button>
)}
</div>
);
}
Error Handling
import { useActionRun } from '@ductape/react';
import { session } from './config';
function ResilientActionExecution() {
const { mutate, isLoading, error, reset } = useActionRun({
onError: (err) => {
console.error('Action failed:', err);
}
});
const handleExecute = () => {
mutate({
app: 'your-app',
action: 'external-api.call',
input: { data: 'test' },
session,
});
};
return (
<div>
<button onClick={handleExecute} disabled={isLoading}>
Execute Action
</button>
{error && (
<div className="error">
<p>Error: {error.message}</p>
<button onClick={() => reset()}>Retry</button>
</div>
)}
</div>
);
}
Next Steps
Vue 3 composables for executing Ductape Actions - pre-built integrations with external services like Stripe and Paystack.
useAction
Execute any Ductape action with reactive state management.
Basic Action Execution
<script setup lang="ts">
import { useAction } from '@ductape/vue';
const { mutate, isLoading, error, data } = useAction({
onSuccess: (result) => {
console.log('Customer created:', result);
alert('Customer created successfully!');
}
});
const handleCreate = () => {
mutate({
action: 'stripe.create-customer',
input: {
email: 'customer@example.com',
name: 'John Doe',
metadata: {
userId: 'user-123'
}
}
});
};
</script>
<template>
<div>
<button @click="handleCreate" :disabled="isLoading">
{{ isLoading ? 'Creating...' : 'Create Customer' }}
</button>
<p v-if="error" class="error">{{ error.message }}</p>
<p v-if="data">Customer ID: {{ data.customerId }}</p>
</div>
</template>
Action with Parameters
<script setup lang="ts">
import { ref } from 'vue';
import { useAction } from '@ductape/vue';
const amount = ref('');
const email = ref('');
const currency = ref('NGN');
const { mutate: initializePayment, isLoading, data } = useAction({
onSuccess: (result) => {
console.log('Payment initialized:', result);
// Redirect to payment page
window.location.href = result.authorization_url;
}
});
const handleSubmit = () => {
initializePayment({
action: 'paystack.initialize-transaction',
input: {
email: email.value,
amount: parseFloat(amount.value) * 100, // Convert to kobo/cents
currency: currency.value,
callback_url: window.location.origin + '/payment/callback'
}
});
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<input
v-model="email"
type="email"
placeholder="Email"
required
/>
<input
v-model="amount"
type="number"
placeholder="Amount"
required
/>
<select v-model="currency">
<option value="NGN">NGN</option>
<option value="USD">USD</option>
<option value="GHS">GHS</option>
</select>
<button type="submit" :disabled="isLoading">
{{ isLoading ? 'Processing...' : 'Pay with Paystack' }}
</button>
</form>
</template>
useActionQuery
Query data from an action (for read operations).
<script setup lang="ts">
import { useActionQuery } from '@ductape/vue';
const { data, isLoading, error } = useActionQuery(
'paystack-transactions',
{
action: 'paystack.list-transactions',
input: {
perPage: 50,
status: 'success'
}
}
);
</script>
<template>
<div v-if="isLoading">Loading transactions...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<ul v-else>
<li v-for="transaction in data?.transactions" :key="transaction.id">
<span>{{ transaction.customer.email }}</span>
<span>{{ transaction.currency }} {{ transaction.amount / 100 }}</span>
<span>{{ new Date(transaction.created_at).toLocaleDateString() }}</span>
</li>
</ul>
</template>
Query with Filters
<script setup lang="ts">
import { ref } from 'vue';
import { useActionQuery } from '@ductape/vue';
const limit = ref(10);
const { data, isLoading } = useActionQuery(
['stripe-customers', limit],
{
action: 'stripe.list-customers',
input: {
limit: limit.value
}
}
);
</script>
<template>
<div>
<select v-model="limit">
<option :value="10">10</option>
<option :value="25">25</option>
<option :value="50">50</option>
<option :value="100">100</option>
</select>
<div v-if="isLoading">Loading...</div>
<table v-else>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<tr v-for="customer in data?.data" :key="customer.id">
<td>{{ customer.name }}</td>
<td>{{ customer.email }}</td>
<td>{{ new Date(customer.created * 1000).toLocaleDateString() }}</td>
</tr>
</tbody>
</table>
</div>
</template>
Common Action Examples
Create Stripe Payment Intent
<script setup lang="ts">
import { useAction } from '@ductape/vue';
const props = defineProps<{
amount: number;
customerId: string;
}>();
const { mutate: createPayment, isLoading, data } = useAction({
onSuccess: (result) => {
console.log('Payment intent created:', result.id);
}
});
const handlePayment = () => {
createPayment({
action: 'stripe.create-payment-intent',
input: {
amount: props.amount * 100, // Convert to cents
currency: 'usd',
customer: props.customerId,
automatic_payment_methods: {
enabled: true
}
}
});
};
</script>
<template>
<div>
<p>Amount: ${{ amount }}</p>
<button @click="handlePayment" :disabled="isLoading">
{{ isLoading ? 'Processing...' : 'Pay Now' }}
</button>
<div v-if="data">
<p>Payment Intent ID: {{ data.id }}</p>
<p>Status: {{ data.status }}</p>
<p>Client Secret: {{ data.client_secret }}</p>
</div>
</div>
</template>
Verify Paystack Transaction
<script setup lang="ts">
import { useActionQuery } from '@ductape/vue';
const props = defineProps<{ reference: string }>();
const { data, isLoading, error } = useActionQuery(
['paystack-verify', props.reference],
{
action: 'paystack.verify-transaction',
input: { reference: props.reference }
}
);
</script>
<template>
<div v-if="isLoading">Verifying payment...</div>
<div v-else-if="error">Verification failed: {{ error.message }}</div>
<div v-else class="verification-result">
<div v-if="data?.data.status === 'success'" class="success">
<h3>Payment Successful!</h3>
<p>Amount: {{ data.data.currency }} {{ data.data.amount / 100 }}</p>
<p>Reference: {{ data.data.reference }}</p>
<p>Paid by: {{ data.data.customer.email }}</p>
</div>
<div v-else class="error">
<h3>Payment Failed</h3>
<p>Status: {{ data?.data.status }}</p>
</div>
</div>
</template>
Create Stripe Subscription
<script setup lang="ts">
import { useAction } from '@ductape/vue';
const props = defineProps<{
customerId: string;
priceId: string;
}>();
const { mutate: createSubscription, isLoading, data } = useAction({
onSuccess: (result) => {
alert('Subscription created!');
console.log('Subscription:', result);
}
});
const handleSubscribe = () => {
createSubscription({
action: 'stripe.create-subscription',
input: {
customer: props.customerId,
items: [{ price: props.priceId }],
payment_behavior: 'default_incomplete',
payment_settings: {
payment_method_types: ['card']
},
expand: ['latest_invoice.payment_intent']
}
});
};
</script>
<template>
<div>
<button @click="handleSubscribe" :disabled="isLoading">
{{ isLoading ? 'Creating Subscription...' : 'Subscribe' }}
</button>
<div v-if="data">
<p>Subscription ID: {{ data.id }}</p>
<p>Status: {{ data.status }}</p>
</div>
</div>
</template>
List Paystack Customers
<script setup lang="ts">
import { ref } from 'vue';
import { useActionQuery } from '@ductape/vue';
const page = ref(1);
const { data, isLoading } = useActionQuery(
['paystack-customers', page],
{
action: 'paystack.list-customers',
input: {
perPage: 20,
page: page.value
}
}
);
</script>
<template>
<div v-if="isLoading">Loading customers...</div>
<div v-else>
<table>
<thead>
<tr>
<th>Email</th>
<th>First Name</th>
<th>Last Name</th>
<th>Customer Code</th>
</tr>
</thead>
<tbody>
<tr v-for="customer in data?.data" :key="customer.id">
<td>{{ customer.email }}</td>
<td>{{ customer.first_name }}</td>
<td>{{ customer.last_name }}</td>
<td>{{ customer.customer_code }}</td>
</tr>
</tbody>
</table>
<div class="pagination">
<button
@click="page = Math.max(1, page - 1)"
:disabled="page === 1"
>
Previous
</button>
<span>Page {{ page }}</span>
<button @click="page++">
Next
</button>
</div>
</div>
</template>
Action Chaining
Execute multiple actions in sequence.
<script setup lang="ts">
import { useAction } from '@ductape/vue';
const props = defineProps<{
email: string;
amount: number;
}>();
const { mutate: createCustomer } = useAction();
const { mutate: createPayment } = useAction();
const { mutate: sendReceipt } = useAction();
const processPayment = async () => {
try {
// Create or retrieve Stripe customer
const customer = await createCustomer({
action: 'stripe.create-customer',
input: {
email: props.email,
metadata: { source: 'web-checkout' }
}
});
// Create payment intent
const payment = await createPayment({
action: 'stripe.create-payment-intent',
input: {
amount: props.amount * 100, // Convert to cents
currency: 'usd',
customer: customer.id,
automatic_payment_methods: { enabled: true }
}
});
// Send receipt via notification
await sendReceipt({
action: 'ductape.send-notification',
input: {
channel: 'email',
to: props.email,
template: 'payment-receipt',
data: {
amount: props.amount,
paymentId: payment.id,
customerName: props.email
}
}
});
alert('Payment processed successfully!');
return payment;
} catch (error) {
console.error('Payment processing failed:', error);
throw error;
}
};
</script>
<template>
<button @click="processPayment">
Process Payment
</button>
</template>
Complete Payment Service Example
<script setup lang="ts">
import { ref } from 'vue';
import { useAction, useActionQuery } from '@ductape/vue';
const amount = ref(99.99);
const email = ref('');
const reference = ref<string | null>(null);
// Create Stripe payment
const { mutate: createStripePayment, isLoading: isCreatingStripe } = useAction({
onSuccess: (result) => {
console.log('Client Secret:', result.client_secret);
}
});
// Create Paystack payment
const { mutate: createPaystackPayment, isLoading: isCreatingPaystack } = useAction({
onSuccess: (result) => {
reference.value = result.data.reference;
window.location.href = result.data.authorization_url;
}
});
// Verify Paystack payment
const { mutate: verifyPaystack } = useAction({
onSuccess: (result) => {
if (result.data.status === 'success') {
alert('Payment verified!');
}
}
});
const handleStripePayment = () => {
createStripePayment({
action: 'stripe.create-payment-intent',
input: {
amount: amount.value * 100,
currency: 'usd',
automatic_payment_methods: { enabled: true }
}
});
};
const handlePaystackPayment = () => {
createPaystackPayment({
action: 'paystack.initialize-transaction',
input: {
email: email.value,
amount: amount.value * 100,
currency: 'NGN',
callback_url: window.location.origin + '/verify'
}
});
};
const handleVerify = () => {
if (!reference.value) return;
verifyPaystack({
action: 'paystack.verify-transaction',
input: { reference: reference.value }
});
};
</script>
<template>
<div class="payment-service">
<h2>Payment Options</h2>
<input
v-model="email"
type="email"
placeholder="Email"
required
/>
<input
v-model.number="amount"
type="number"
placeholder="Amount"
required
/>
<div class="payment-buttons">
<button
@click="handleStripePayment"
:disabled="isCreatingStripe"
>
{{ isCreatingStripe ? 'Processing...' : 'Pay with Stripe' }}
</button>
<button
@click="handlePaystackPayment"
:disabled="isCreatingPaystack || !email"
>
{{ isCreatingPaystack ? 'Processing...' : 'Pay with Paystack' }}
</button>
</div>
<button
v-if="reference"
@click="handleVerify"
>
Verify Payment
</button>
</div>
</template>
<style scoped>
.payment-service {
max-width: 500px;
margin: 0 auto;
padding: 2rem;
}
.payment-buttons {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
</style>