Charge repeat customers
Repeat customers expect fast, frictionless checkout. Instead of asking them to re-enter their name, email, and payment details every time, use the customer and payment method IDs from their first purchase to create instant checkout flows. This guide shows you how to charge returning customers with saved payment methods and how to handle cases where they want to use a different payment method.
An authenticated AI agent can look up the saved customer context, create the next order after confirmation, and fetch the resulting order while your app handles payment confirmation.
MCP tools: get_customer, create_order or get_order
Confirmed MCP actions require explicit form confirmation before Inttegro changes state.
How it works
After a customer's first successful payment, Inttegro automatically creates a customer record and attaches their payment method to it. You'll find customer.id and payment_method.id in the order response. Store these IDs in your database linked to your user account. When the customer returns, reference them with customer_id and optionally payment_method_id instead of passing full details. Inttegro handles OTP delivery and payment confirmation just like the first time, but checkout is faster because you're not collecting information again.
Choose your integration approach
Select how you want to charge repeat customers:
- With saved payment method
- With new payment method
The simplest repeat customer flow uses both customer_id and payment_method_id for instant, one-click checkout. The customer doesn't need to re-enter any information—just confirm the OTP and the payment completes.
Charge with saved method
- cURL
- TypeScript
- Go
- Python
- PHP
- Ruby
- Java
- C#
curl https://api.inttegro.com/orders/create \
-H "Authorization: Bearer $INTTEGRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"request_meta": {
"idempotency_key": "order_repeat_001"
},
"execute_payment": true,
"customer_id": "cu_abc123",
"payment_method_id": "pm_saved_method",
"line_items": [
{
"type": "product",
"product": {
"type": "digital",
"name": "Premium Subscription",
"quantity": 1,
"price": {
"currency": "ghs",
"value": 5000
}
}
}
]
}'
import * as Inttegro from '@inttegro/inttegro-sdk'
const inttegro = new Inttegro.InttegroClient({
apiKey: process.env.INTTEGRO_API_KEY!,
})
const result = await inttegro.orders.create({
requestMeta: {
idempotencyKey: "order_repeat_001",
},
executePayment: true,
customerId: "cu_abc123",
paymentMethodId: "pm_saved_method",
lineItems: [
{
type: Inttegro.LineItemTypes.Product,
product: {
type: Inttegro.ProductTypes.Digital,
name: "Premium Subscription",
quantity: 1,
price: {
currency: "ghs",
value: 5000,
},
},
},
],
})
package main
import (
"context"
"log"
"os"
inttegro "github.com/zebodotdev/inttegro-sdk-go/v4"
"github.com/zebodotdev/inttegro-sdk-go/v4/money"
)
func main() {
ctx := context.Background()
client := inttegro.NewClient(os.Getenv("INTTEGRO_API_KEY"))
params := inttegro.OrderCreateParams{
RequestMeta: &inttegro.RequestMeta{
IdempotencyKey: "order_repeat_001",
},
ExecutePayment: inttegro.Bool(true),
CustomerID: "cu_abc123",
PaymentMethodID: "pm_saved_method",
LineItems: []inttegro.OrderLineItemParams{
{
Type: inttegro.LineItemTypeProduct,
Product: &inttegro.ProductLineItemParams{
Type: inttegro.ProductTypeDigital,
Name: "Premium Subscription",
Quantity: 1,
Price: inttegro.PriceParams{
AmountParams: money.AmountParams{
Currency: money.GHS,
Value: 5000,
},
},
},
},
},
}
result, err := client.Orders.Create(ctx, params)
if err != nil {
log.Fatal(err)
}
_ = result
}
import os
import inttegro
client = inttegro.InttegroClient(api_key=os.environ["INTTEGRO_API_KEY"])
result = client.orders.create(inttegro.orders.CreateForCustomerRequest(
request_meta=inttegro.CreateOrderExistingCustomerInputRequestMeta(
idempotency_key="order_repeat_001",
),
execute_payment=True,
customer_id="cu_abc123",
payment_method_id="pm_saved_method",
line_items=[
inttegro.orders.ProductLineItem(
type=inttegro.LineItemType.PRODUCT,
product=inttegro.orders.Product(
type=inttegro.ProductType.DIGITAL,
name="Premium Subscription",
quantity=1,
price=inttegro.orders.PriceParams(
currency=inttegro.Currency.GHS,
value=5000,
),
),
),
],
))
<?php
use Inttegro\Client;
$client = new Client($_ENV['INTTEGRO_API_KEY']);
$result = $client->orders->create([
'request_meta' => [
'idempotency_key' => 'order_repeat_001',
],
'execute_payment' => true,
'customer_id' => 'cu_abc123',
'payment_method_id' => 'pm_saved_method',
'line_items' => [
[
'type' => \Inttegro\LineItemType::Product,
'product' => [
'type' => \Inttegro\ProductType::Digital,
'name' => 'Premium Subscription',
'quantity' => 1,
'price' => [
'currency' => 'ghs',
'value' => 5000,
],
],
],
],
]);
require "inttegro"
client = Inttegro::Client.new(api_key: ENV.fetch("INTTEGRO_API_KEY"))
result = client.orders.create(
request_meta: {
idempotency_key: "order_repeat_001",
},
execute_payment: true,
customer_id: "cu_abc123",
payment_method_id: "pm_saved_method",
line_items: [
{
type: Inttegro::LineItemType::PRODUCT,
product: {
type: Inttegro::ProductType::DIGITAL,
name: "Premium Subscription",
quantity: 1,
price: {
currency: "ghs",
value: 5000,
},
},
},
]
)
import com.inttegro.Client;
import com.inttegro.orders.OrderCreateParams;
import com.inttegro.RequestMeta;
import com.inttegro.orders.OrderLineItemParams;
import com.inttegro.orders.ProductLineItemParams;
import com.inttegro.products.ProductType;
import com.inttegro.prices.PriceParams;
import com.inttegro.money.Currency;
public class Example {
public static void main(String[] args) throws Exception {
var client = new Client(System.getenv("INTTEGRO_API_KEY"));
var params = OrderCreateParams.builder()
.requestMeta(RequestMeta.builder()
.idempotencyKey("order_repeat_001")
.build())
.executePayment(true)
.customerId("cu_abc123")
.paymentMethodId("pm_saved_method")
.lineItem(OrderLineItemParams.product(ProductLineItemParams.builder()
.type(ProductType.DIGITAL)
.name("Premium Subscription")
.quantity(1L)
.price(PriceParams.of(Currency.GHS, 5000))
.build()))
.build();
var result = client.orders().create(params);
}
}
using Inttegro;
using var inttegro = new InttegroClient(
Environment.GetEnvironmentVariable("INTTEGRO_API_KEY")!
);
var result = await inttegro.Orders.CreateAsync(new {
request_meta = new {
idempotency_key = "order_repeat_001",
},
execute_payment = true,
customer_id = "cu_abc123",
payment_method_id = "pm_saved_method",
line_items = new[] {
new {
type = Inttegro.LineItemType.Product,
product = new {
type = Inttegro.ProductType.Digital,
name = "Premium Subscription",
quantity = 1,
price = new {
currency = "ghs",
value = 5000,
},
},
},
},
});
Response
This is a partial response showing key attributes. See the complete Order object for all available fields.
{
"order": {
"id": "or_repeat_xyz",
"status": "requires_payment",
"customer": {
"id": "cu_abc123",
"name": "Gloria Kesewaa",
},
"payment": {
"id": "py_new_attempt",
"status": "requires_action",
"payment_method": {
"id": "pm_saved_method",
"type": "mobile_money",
"network": "mtn",
"account_number": "0544998605"
},
"next_action": {
"type": "confirm_payment",
"confirm_payment": {
"expires_at": "2025-01-13T10:08:00Z",
"request": {
"recipient": "0544998605",
"sent_via": "sms"
}
}
}
}
}
}
What's different:
- No
customer_dataorpayment_method_dataneeded—everything is already on file - Inttegro sends OTP to the phone number associated with the saved payment method
- Customer confirms with OTP just like the first purchase
The confirmation step is identical to first-time payments. Submit the returned order ID, payment ID, confirmation-request ID, and the customer's token as shown in Confirm a payment.
Sometimes a repeat customer wants to use a different payment method—maybe they're using a different mobile money provider or their old number changed. Provide customer_id with new payment_method_data to attach the new payment method to the existing customer. Inttegro creates a new payment method record and charges it, while keeping the customer's profile and history intact.
Charge with new method
- cURL
- TypeScript
- Go
- Python
- PHP
- Ruby
- Java
- C#
curl https://api.inttegro.com/orders/create \
-H "Authorization: Bearer $INTTEGRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"request_meta": {
"idempotency_key": "order_new_method_001"
},
"execute_payment": true,
"customer_id": "cu_abc123",
"payment_method_data": {
"type": "mobile_money",
"mobile_money": {
"network": "telecel",
"account_number": "0501234567"
}
},
"line_items": [
{
"type": "product",
"product": {
"type": "physical",
"name": "Running Shoes",
"quantity": 1,
"price": {
"currency": "ghs",
"value": 35000
}
}
}
]
}'
import * as Inttegro from '@inttegro/inttegro-sdk'
const inttegro = new Inttegro.InttegroClient({
apiKey: process.env.INTTEGRO_API_KEY!,
})
const result = await inttegro.orders.create({
requestMeta: {
idempotencyKey: "order_new_method_001",
},
executePayment: true,
customerId: "cu_abc123",
paymentMethodData: {
type: Inttegro.PaymentMethodTypes.MobileMoney,
mobileMoney: {
network: Inttegro.MobileMoneyNetworks.Telecel,
accountNumber: "0501234567",
},
},
lineItems: [
{
type: Inttegro.LineItemTypes.Product,
product: {
type: Inttegro.ProductTypes.Physical,
name: "Running Shoes",
quantity: 1,
price: {
currency: "ghs",
value: 35000,
},
},
},
],
})
package main
import (
"context"
"log"
"os"
inttegro "github.com/zebodotdev/inttegro-sdk-go/v4"
"github.com/zebodotdev/inttegro-sdk-go/v4/money"
"github.com/zebodotdev/inttegro-sdk-go/v4/paymentmethods"
)
func main() {
ctx := context.Background()
client := inttegro.NewClient(os.Getenv("INTTEGRO_API_KEY"))
params := inttegro.OrderCreateParams{
RequestMeta: &inttegro.RequestMeta{
IdempotencyKey: "order_new_method_001",
},
ExecutePayment: inttegro.Bool(true),
CustomerID: "cu_abc123",
PaymentMethodData: &inttegro.PaymentMethodData{
Type: inttegro.PaymentMethodTypeMobileMoney,
MobileMoney: &inttegro.MobileMoneyParams{
Network: paymentmethods.MobileMoneyNetworkTelecel,
AccountNumber: "0501234567",
},
},
LineItems: []inttegro.OrderLineItemParams{
{
Type: inttegro.LineItemTypeProduct,
Product: &inttegro.ProductLineItemParams{
Type: inttegro.ProductTypePhysical,
Name: "Running Shoes",
Quantity: 1,
Price: inttegro.PriceParams{
AmountParams: money.AmountParams{
Currency: money.GHS,
Value: 35000,
},
},
},
},
},
}
result, err := client.Orders.Create(ctx, params)
if err != nil {
log.Fatal(err)
}
_ = result
}
import os
import inttegro
client = inttegro.InttegroClient(api_key=os.environ["INTTEGRO_API_KEY"])
result = client.orders.create(inttegro.orders.CreateForCustomerRequest(
request_meta=inttegro.CreateOrderExistingCustomerInputRequestMeta(
idempotency_key="order_new_method_001",
),
execute_payment=True,
customer_id="cu_abc123",
payment_method_data=inttegro.orders.PaymentMethod(
type=inttegro.PaymentMethodType.MOBILE_MONEY,
mobile_money=inttegro.orders.MobileMoney(
network=inttegro.MobileMoneyNetwork.TELECEL,
account_number="0501234567",
),
),
line_items=[
inttegro.orders.ProductLineItem(
type=inttegro.LineItemType.PRODUCT,
product=inttegro.orders.Product(
type=inttegro.ProductType.PHYSICAL,
name="Running Shoes",
quantity=1,
price=inttegro.orders.PriceParams(
currency=inttegro.Currency.GHS,
value=35000,
),
),
),
],
))
<?php
use Inttegro\Client;
$client = new Client($_ENV['INTTEGRO_API_KEY']);
$result = $client->orders->create([
'request_meta' => [
'idempotency_key' => 'order_new_method_001',
],
'execute_payment' => true,
'customer_id' => 'cu_abc123',
'payment_method_data' => [
'type' => \Inttegro\PaymentMethodType::MobileMoney,
'mobile_money' => [
'network' => \Inttegro\MobileMoneyNetwork::Telecel,
'account_number' => '0501234567',
],
],
'line_items' => [
[
'type' => \Inttegro\LineItemType::Product,
'product' => [
'type' => \Inttegro\ProductType::Physical,
'name' => 'Running Shoes',
'quantity' => 1,
'price' => [
'currency' => 'ghs',
'value' => 35000,
],
],
],
],
]);
require "inttegro"
client = Inttegro::Client.new(api_key: ENV.fetch("INTTEGRO_API_KEY"))
result = client.orders.create(
request_meta: {
idempotency_key: "order_new_method_001",
},
execute_payment: true,
customer_id: "cu_abc123",
payment_method_data: {
type: Inttegro::PaymentMethodType::MOBILE_MONEY,
mobile_money: {
network: Inttegro::MobileMoneyNetwork::TELECEL,
account_number: "0501234567",
},
},
line_items: [
{
type: Inttegro::LineItemType::PRODUCT,
product: {
type: Inttegro::ProductType::PHYSICAL,
name: "Running Shoes",
quantity: 1,
price: {
currency: "ghs",
value: 35000,
},
},
},
]
)
import com.inttegro.Client;
import com.inttegro.orders.OrderCreateParams;
import com.inttegro.RequestMeta;
import com.inttegro.paymentmethods.PaymentMethodData;
import com.inttegro.paymentmethods.PaymentMethodType;
import com.inttegro.paymentmethods.MobileMoneyParams;
import com.inttegro.paymentmethods.MobileMoneyNetwork;
import com.inttegro.orders.OrderLineItemParams;
import com.inttegro.orders.ProductLineItemParams;
import com.inttegro.products.ProductType;
import com.inttegro.prices.PriceParams;
import com.inttegro.money.Currency;
public class Example {
public static void main(String[] args) throws Exception {
var client = new Client(System.getenv("INTTEGRO_API_KEY"));
var params = OrderCreateParams.builder()
.requestMeta(RequestMeta.builder()
.idempotencyKey("order_new_method_001")
.build())
.executePayment(true)
.customerId("cu_abc123")
.paymentMethodData(PaymentMethodData.builder()
.type(PaymentMethodType.MOBILE_MONEY)
.mobileMoney(MobileMoneyParams.builder()
.network(MobileMoneyNetwork.TELECEL)
.accountNumber("0501234567")
.build())
.build())
.lineItem(OrderLineItemParams.product(ProductLineItemParams.builder()
.type(ProductType.PHYSICAL)
.name("Running Shoes")
.quantity(1L)
.price(PriceParams.of(Currency.GHS, 35000))
.build()))
.build();
var result = client.orders().create(params);
}
}
using Inttegro;
using var inttegro = new InttegroClient(
Environment.GetEnvironmentVariable("INTTEGRO_API_KEY")!
);
var result = await inttegro.Orders.CreateAsync(new {
request_meta = new {
idempotency_key = "order_new_method_001",
},
execute_payment = true,
customer_id = "cu_abc123",
payment_method_data = new {
type = Inttegro.PaymentMethodType.MobileMoney,
mobile_money = new {
network = Inttegro.MobileMoneyNetwork.Telecel,
account_number = "0501234567",
},
},
line_items = new[] {
new {
type = Inttegro.LineItemType.Product,
product = new {
type = Inttegro.ProductType.Physical,
name = "Running Shoes",
quantity = 1,
price = new {
currency = "ghs",
value = 35000,
},
},
},
},
});
Response
This is a partial response showing key attributes. See the complete Order object for all available fields.
{
"order": {
"id": "or_new_method_xyz",
"status": "requires_payment",
"customer": {
"id": "cu_abc123",
"name": "Gloria Kesewaa",
},
"payment": {
"id": "py_new_method",
"status": "requires_action",
"payment_method": {
"id": "pm_new_telecel",
"type": "mobile_money",
"network": "telecel",
"account_number": "0501234567"
},
"next_action": {
"type": "confirm_payment",
"confirm_payment": {
"expires_at": "2025-01-13T10:15:00Z",
"request": {
"recipient": "0501234567",
"sent_via": "sms"
}
}
}
}
}
}
What happened:
- New
payment_method.idcreated (pm_new_telecel) and attached to existing customer - OTP sent to the new phone number (0501234567)
- Customer record remains the same, but now has multiple payment methods on file
- Save the new
payment_method.idfor future use
Common patterns
The TypeScript SDK returns the order domain object directly:
import { InttegroClient } from '@inttegro/inttegro-sdk'
const inttegro = new InttegroClient({
apiKey: process.env.INTTEGRO_API_KEY!,
})
async function createOrder(payload: Parameters<typeof inttegro.orders.create>[0]) {
return inttegro.orders.create(payload)
}
Subscription renewals
For recurring subscriptions, store the customer and payment method IDs when they subscribe, then charge them automatically:
async function renewSubscription(subscriptionId: string) {
const subscription = await db.subscriptions.findOne({ id: subscriptionId })
const order = await createOrder({
request_meta: {
idempotency_key: `sub_${subscriptionId}_${new Date().toISOString()}`,
},
execute_payment: true,
customer_id: subscription.inttegro_customer_id,
payment_method_id: subscription.inttegro_payment_method_id,
line_items: [
{
type: 'product',
product: {
type: 'digital',
name: `${subscription.plan_name} - Monthly`,
quantity: 1,
price: subscription.price,
},
},
],
})
// For subscriptions, Inttegro may auto-confirm some payment methods
// Check if requires_action or already paid
if (order.payment.status === 'requires_action') {
// Send OTP notification to customer
await notifyCustomer({
email: subscription.customer_email,
message:
'Please confirm your subscription renewal with the OTP sent to your phone',
})
} else if (order.payment.status === 'paid') {
// Payment completed automatically
await updateSubscription(subscriptionId, { last_payment_date: new Date() })
}
}
Testing
Use an application configured for the test environment. Create the first test order to obtain customer and payment-method IDs, then use those IDs on the repeat order. If the response requires confirmation, follow Confirm a payment with order_id, payment_id, confirmation_id, and the test token supplied for that environment. Do not hardcode a universal token value.
Key tips
Store both IDs: Always save customer.id and payment_method.id from the response after the first payment. You need the customer ID to associate future orders and the payment method ID for fast checkout.
Customer profile vs payment method: A customer can have multiple payment methods. When you use customer_id + payment_method_data, you're adding a new payment method to an existing customer. When you use customer_id + payment_method_id, you're charging an existing payment method.
Payment method verification: All payment methods require OTP confirmation, even saved ones. This protects both you and the customer—it proves the customer has access to the phone at the time of payment.
Handle expired payment methods: Phone numbers change, accounts get closed. If a payment fails with an expired or invalid payment method error, prompt the customer to add a new payment method using the customer_id + payment_method_data pattern.
Next steps
- Accept a payment - First-time customer payment flow
- Retry a failed payment - Retry failed payments with alternative methods
- Order now, pay later - Defer payment to a later time
- Orders API - Complete API reference
You now know how to build fast, one-click checkout for repeat customers while maintaining security through OTP verification.