Référence API

Référence complète du paramètre avec des exemples de TypeScript. Tous les exemples utilisent le handypay aide de la Démarrer rapidement- Oui. L'outil peut télécharger le Contrat OpenAPI 3.1.

Base URL

https://api.handypay.me/api/v1
bash

Version API: 2025-01-01 (retourné X-API-Version en-tête sur chaque réponse).

Authentification

Toutes les demandes nécessitent un jeton porteur dans le Authorization Entête.

Authorization: Bearer hp_live_your_api_key_here
javascript
  • Clés préfixées avec hp_live_ sont destinés à la production.
  • Clés préfixées avec hp_test_ sont pour sandbox/testing.
  • Générer et gérer les clés à partir de la Portail marchand.

Le mode d'essai est complètement isolé

Tous les hp_test_ demande utilise un compte de test dédié. Les produits de test, les clients, les paiements, les abonnements et les paramètres de webhook ne sont jamais apparus dans l'activité en direct. Voir dans le Espace de travail du mode d'essai.

Liste des paiements effectués pour les tests
curl https://api.handypay.me/api/v1/test-payments \
  -H "Authorization: Bearer hp_test_your_api_key_here"
bash

Limites de taux

1000 demandes par heure par clé API. Dépassant les limites de rendement 429 avec une Retry-After Entête.

Format de réponse

Chaque réponse est enveloppée dans une enveloppe standard.

Succès

JSON
{
  "success": true,
  "data": { ... },
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
json

Erreur

JSON
{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "Name is required"
  },
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
json

Pagination

Tous les paramètres de la liste utilisent la pagination par curseur.

ChampTypeRequisDésignation des marchandises
limitnumberNuméroArticles par page (1–100, par défaut 10)
starting_afterstringNuméroID du dernier article de la page précédente

La réponse comprend has_more: true lorsque des pages supplémentaires existent.

Produits

Créer et gérer des produits pour des achats ponctuels.

MéthodeVoieDésignation des marchandises
POST/v1/productsCréer un produit
GET/v1/productsDénomination des produits
GET/v1/products/:idObtenez un produit
PUT/v1/products/:idMettre à jour un produit
DELETE/v1/products/:idArchiver un produit

Créer un produit

TypeScript
const product = await handypay("/products", {
  method: "POST",
  body: JSON.stringify({
    name: "Premium Plan",
    description: "Access to all features",
    price: {
      amount: 2999,
      currency: "usd",
    },
  }),
});

console.log(product.id); // "prod_abc123"
typescript
exemple de cURL
CURL
curl -X POST https://api.handypay.me/api/v1/products \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Premium Plan",
    "description": "Access to all features",
    "price": {
      "amount": 2999,
      "currency": "usd"
    }
  }'
bash

Organisme de demande

ChampTypeRequisDésignation des marchandises
namestringOuiNom du produit
descriptionstringNuméroDescription du produit
imagesstring[]NuméroJusqu'à 8 URLs d'images
metadataobjectNuméroPaires personnalisées de valeurs de clés (jusqu'à 50 clés)
activebooleanNuméroPar défaut vrai
urlstringNuméroPage produit URL sur votre site
shippablebooleanNuméroIndique si le produit nécessite une expédition
unit_labelstringNuméroÉtiquette par unité (p. ex. "siège", "licence")
statement_descriptorstringNuméroTexte de l'état bancaire (max. 22 caractères)
tax_codestringNuméroCode de la taxe Stripe
price.amountnumberNuméroPrix en unité monétaire la plus petite (en cents)
price.currencystringNuméroCode de devise ISO 4217 (par exemple "usd", "jmd")
price.tax_behaviorstringNuméroinclusivement, exclusivement ou non

Clients

Gérer les dossiers clients pour les achats et les abonnements répétés.

MéthodeVoieDésignation des marchandises
POST/v1/customersCréer un client
GET/v1/customersListe des clients
GET/v1/customers/:idObtenir un client
PUT/v1/customers/:idMettre à jour un client
DELETE/v1/customers/:idSupprimer un client

Créer un client

TypeScript
const customer = await handypay("/customers", {
  method: "POST",
  body: JSON.stringify({
    email: "customer@example.com",
    name: "Jane Doe",
  }),
});

console.log(customer.id); // "cus_abc123"
typescript
exemple de cURL
CURL
curl -X POST https://api.handypay.me/api/v1/customers \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "name": "Jane Doe"
  }'
bash

Organisme de demande

ChampTypeRequisDésignation des marchandises
emailstringOuiCourriel du client
namestringNuméroNom du client
phonestringNuméroNuméro de téléphone du client
metadataobjectNuméroPaires personnalisées de valeurs de clé

Séances de paiement

Créer des sessions de paiement hébergées pour des paiements ponctuels. Le prix standard de HandyPay s'applique: 4,9% + US$0.40 par transaction sur le forfait gratuit, ou 4,2% + US$0.40 sur Pro. Il n'y a pas de frais supplémentaires sur API ou plate-forme en plus.

MéthodeVoieDésignation des marchandises
POST/v1/payment-sessionsCréer une session de paiement
GET/v1/payment-sessions/:idObtenir l'état de la session
GET/v1/test-paymentsListe des paiements de test (hp_test_ seulement)

Créer une session de paiement (avec le prix existant)

TypeScript
const session = await handypay("/payment-sessions", {
  method: "POST",
  body: JSON.stringify({
    line_items: [{ price_id: "price_abc123", quantity: 1 }],
    success_url: "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://yoursite.com/cancel",
  }),
});

// Redirect customer to checkout
window.location.href = session.url;
typescript
exemple de cURL
CURL
curl -X POST https://api.handypay.me/api/v1/payment-sessions \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [{ "price_id": "price_abc123", "quantity": 1 }],
    "success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yoursite.com/cancel"
  }'
bash

Créer une session de paiement (montant personnalisé)

TypeScript
const session = await handypay("/payment-sessions", {
  method: "POST",
  body: JSON.stringify({
    line_items: [{
      amount: 5000,
      currency: "usd",
      name: "Custom Order",
      quantity: 1,
    }],
    success_url: "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://yoursite.com/cancel",
  }),
});
typescript
exemple de cURL
CURL
curl -X POST https://api.handypay.me/api/v1/payment-sessions \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [{
      "amount": 5000,
      "currency": "usd",
      "name": "Custom Order",
      "quantity": 1
    }],
    "success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yoursite.com/cancel"
  }'
bash

Organisme de demande

ChampTypeRequisDésignation des marchandises
line_itemsarrayOuiAu moins une ligne
success_urlstringOuiRediriger URL après le paiement réussi
cancel_urlstringOuiRediriger URL si le client annule
customer_idstringNuméroClient existant ID
customer_emailstringNuméroPréremplir l’e-mail (si aucun customer_id)
pass_fees_to_customerbooleanNuméroAjouter les frais de traitement et de service au total du client
metadataobjectNuméroPaires personnalisées de valeurs de clé
collect_shipping_addressbooleanNuméroRecueillir une adresse d'expédition
billing_address_collectionstringNuméroauto ou obligatoire
shipping_countriesstring[]NuméroPays de destination autorisés ISO-2
shipping_optionsarrayNuméroÉtiquettes et montants d'expédition dans la plus petite unité monétaire

Champs des rubriques

ChampTypeRequisDésignation des marchandises
price_idstringNuméroStripe Price ID existant
amountnumberNuméroMontant personnalisé en cents
currencystringNuméroMontant requis
namestringNuméroMontant requis
quantitynumberOuiQuantité

Fournir les deux price_id ou amount+currency+name par article de ligne.

HandyPay substituts {CHECKOUT_SESSION_ID} dans le succès URL. Interrogez ID avec le même marchand et le même mode clé live/test qui l'a créé. La session reste interrogeable après l'achèvement, mais votre webhook signé devrait toujours conduire l'exécution de facture.

Paiements intégrés

Créez un PaymentIntent depuis votre serveur lorsque vous souhaitez afficher Stripe Elements sur votre propre page de paiement. Votre clé API HandyPay reste côté serveur ; envoyez au navigateur uniquement la clé publiable renvoyée, l’ID du compte connecté et le secret client à courte durée de vie.

MéthodeVoieDésignation des marchandises
POST/v1/payment-intentsCréer un PaymentIntent intégré
TypeScript côté serveur
const intent = await handypay("/payment-intents", {
  method: "POST",
  body: JSON.stringify({
    amount: 5000,
    currency: "ttd",
    description: "Order #1042",
    customer_email: "buyer@example.com",
    pass_fees_to_customer: true,
    metadata: { order_id: "1042" },
  }),
});

// Pass these values to Stripe.js/Elements. Never pass HANDYPAY_API_KEY.
return {
  clientSecret: intent.client_secret,
  publishableKey: intent.publishable_key,
  stripeAccount: intent.stripe_account,
};
typescript
ChampTypeRequisDésignation des marchandises
amountnumberOuiEntier positif dans la plus petite unité monétaire
currencystringOuiISO 4217 code de devise à trois lettres
descriptionstringNuméroDescription du paiement
customer_emailstringNuméroCourriel du client pour les reçus et le rapprochement
pass_fees_to_customerbooleanNuméroAugmentation du montant pour couvrir les frais
metadataobjectNuméroIdentifiants de votre commande ou facture

Montant des restitutions

Remboursement d'un paiement appartenant au marchand authentifié. HandyPay vérifie la propriété du paiement et le solde disponible avant de créer l'inversion. Omettre amount pour un remboursement complet.

MéthodeVoieDésignation des marchandises
POST/v1/refundsCréer un remboursement intégral ou partiel
TypeScript
const refund = await handypay("/refunds", {
  method: "POST",
  body: JSON.stringify({
    session_id: "cs_live_...",
    amount: 2500,
    reason: "requested_by_customer",
  }),
});

console.log(refund.id, refund.status);
typescript
ChampTypeRequisDésignation des marchandises
session_idstringConditionnelID de Checkout Session (cs_...). Utilisez ce champ ou payment_intent
payment_intentstringConditionnelID du PaymentIntent (pi_...). Utilisez ce champ ou session_id
amountnumberNuméroMontant du remboursement partiel dans la plus petite unité monétaire
reasonstringNuméroduplicate, fraudulent ou requested_by_customer

Un paiement contesté, entièrement remboursé, entrecroisé ou insuffisant est rejeté sans créer de remboursement.

Différends

Examiner les frais de remise pour le commerçant connecté et fournir des preuves avant la date d'échéance.

MéthodeVoieDésignation des marchandises
GET/v1/disputesListe des litiges
GET/v1/disputes/:idObtenez un différend et son état de preuve
POST/v1/disputes/:id/evidenceMettre à jour ou présenter des éléments de preuve
TypeScript
const dispute = await handypay("/disputes/dp_123/evidence", {
  method: "POST",
  body: JSON.stringify({
    evidence: {
      customer_email_address: "buyer@example.com",
      product_description: "Annual software subscription",
      customer_communication: "https://files.example.com/evidence/1042.pdf",
    },
    submit: false,
  }),
});
typescript
ChampTypeRequisDésignation des marchandises
evidenceobjectNuméroStripe champs de preuve de litige comme des valeurs de chaîne
submitbooleanNuméroNe le confirmer que lorsque les preuves sont complètes et prêtes à être examinées

Abonnements

Créez des produits récurrents et gérez les abonnements.

Produits d'abonnement

MéthodeVoieDésignation des marchandises
POST/v1/subscription-productsCréer un produit d'abonnement
GET/v1/subscription-productsListe des produits d'abonnement

Sessions d'abonnement et gestion

MéthodeVoieDésignation des marchandises
POST/v1/subscription-sessionsCréer un abonnement à la caisse
GET/v1/subscriptionsListe des abonnements actifs
PATCH/v1/subscriptions/:id/quantityChanger les sièges avec une proration explicite
POST/v1/subscriptions/:id/cancelAnnuler à la fin de la période de facturation

Intervalles de facturation pris en charge

weeklybi-weeklymonthlybi-monthlyquarterlysemi-annualannual

Créer un produit d'abonnement

TypeScript
const subProduct = await handypay("/subscription-products", {
  method: "POST",
  body: JSON.stringify({
    name: "Pro Plan",
    description: "Monthly pro access",
    amount: 1999,
    currency: "usd",
    interval: "monthly",
    trial_period_days: 14,
  }),
});

console.log(subProduct.price.id); // Use this price_id for subscription sessions
typescript
exemple de cURL
CURL
curl -X POST https://api.handypay.me/api/v1/subscription-products \
  -H "Authorization: Bearer hp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro Plan",
    "description": "Monthly pro access",
    "amount": 1999,
    "currency": "usd",
    "interval": "monthly",
    "trial_period_days": 14
  }'
bash

Organisme de demande

ChampTypeRequisDésignation des marchandises
namestringOuiNom du produit
descriptionstringNuméroDescription du produit
amountnumberOuiPrix en unité monétaire la plus petite
currencystringOuiCode de devise ISO 4217
intervalstringOuiUn des intervalles supportés
trial_period_daysnumberNuméroDurée de l'essai libre en jours
metadataobjectNuméroPaires personnalisées de valeurs de clé

Créer une caisse avec plusieurs sièges

TypeScript
const session = await handypay("/subscription-sessions", {
  method: "POST",
  body: JSON.stringify({
    price_id: subProduct.price.id,
    quantity: 5,
    customer_email: "buyer@example.com",
    success_url: "https://example.com/success",
    cancel_url: "https://example.com/plans",
  }),
});
typescript

Changer de siège sur un abonnement actif

La quantité doit être un entier de 1 à 1 000. Choisissez comment le rajustement de facturation est géré au lieu de compter sur un défaut implicite.

TypeScript
const updated = await handypay("/subscriptions/sub_123/quantity", {
  method: "PATCH",
  body: JSON.stringify({
    quantity: 8,
    proration_behavior: "create_prorations",
  }),
});
typescript
ChampTypeRequisDésignation des marchandises
quantitynumberOuiNombre de nouveaux sièges de 1 à 1 000
proration_behaviorstringNumérocreate_prorations, always_invoice ou none
item_idstringNuméroPoste d'abonnement spécifique lorsqu'un abonnement comporte plusieurs produits

Webhooks

Recevez des notifications d'événements en temps réel via HTTP POST sur vos paramètres.

MéthodeVoieDésignation des marchandises
POST/v1/webhook-endpointsEnregistrer un paramètre
GET/v1/webhook-endpointsListe des paramètres
DELETE/v1/webhook-endpoints/:idDésactiver un paramètre
  • Les points d'arrivée doivent utiliser HTTPS.
  • Les paramètres Webhook enregistrés avec un hp_test_ clé recevoir des événements de test seulement. Les destinations en direct et les destinations de test sont stockées séparément.
  • Chaque paramètre actif souscrit à un événement reçoit une livraison indépendante signée avec son propre secret. Ne réutilisez pas le secret d'un point de départ pour un autre.
  • Retourner une réponse 2xx dans les 10 secondes. Stocker chaque événement id avant les effets secondaires, les livraisons en double sont donc sûres.
  • Après 10 échecs consécutifs, un paramètre est automatiquement désactivé.

Types d'événements pris en charge

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • checkout.session.completed
  • checkout.session.expired
  • checkout.session.async_payment_succeeded
  • checkout.session.async_payment_failed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • charge.refunded
  • charge.dispute.created
  • charge.dispute.closed
Traitez les webhooks signés comme la source de paiement de la vérité — pas la redirection de succès du navigateur. A payment_intent.payment_failed l’événement identifie le PaymentIntent dans data.id; utilisez vos métadonnées (par exemple order_id) pour la réconcilier. Utilisation checkout.session.expired pour l'expiration de la session et les événements d'async pour les méthodes de paiement différé.

Vérification des signatures de webhook

Chaque livraison comprend une X-HandyPay-Signature en-tête dans le format sha256={hex}- Oui. Vérifiez en calculant HMAC-SHA256 du corps de requête brute en utilisant le secret de signature de votre point de départ:

TypeScript
import { createHmac, timingSafeEqual } from "node:crypto";

function verifySignature(
  payload: string | Buffer,
  secret: string,
  signature: string
): boolean {
  const prefix = "sha256=";
  if (!signature.startsWith(prefix)) return false;
  const hex = signature.slice(prefix.length);
  if (!/^[0-9a-f]{64}$/i.test(hex)) return false;

  const expected = createHmac("sha256", secret).update(payload).digest();
  const received = Buffer.from(hex, "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}
typescript

Gestionnaire de route API Next.js

app/api/webhooks/handypay/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createHmac, timingSafeEqual } from "node:crypto";

export const runtime = "nodejs";
const SECRET = process.env.HANDYPAY_WEBHOOK_SECRET;
if (!SECRET) throw new Error("HANDYPAY_WEBHOOK_SECRET is not configured");

function hasValidSignature(payload: string, signature: string): boolean {
  const prefix = "sha256=";
  if (!signature.startsWith(prefix)) return false;
  const hex = signature.slice(prefix.length);
  if (!/^[0-9a-f]{64}$/i.test(hex)) return false;
  const expected = createHmac("sha256", SECRET).update(payload).digest();
  const received = Buffer.from(hex, "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("x-handypay-signature") ?? "";
  if (!hasValidSignature(body, signature)) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  let event: { id: string; type: string; data: unknown };
  try {
    event = JSON.parse(body);
  } catch {
    return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
  }

  // Persist event.id before side effects so redeliveries can be ignored safely.
  switch (event.type) {
    case "checkout.session.completed":
      // Fulfill an immediate payment.
      break;
    case "checkout.session.async_payment_succeeded":
      // Fulfill a delayed payment.
      break;
    case "charge.refunded":
      // Mark the matching order as refunded.
      break;
  }

  return NextResponse.json({ received: true });
}
typescript
Gestionnaire Express.js
routes/webhooks.ts
// Register this route BEFORE app.use(express.json()).
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const router = express.Router();
const SECRET = process.env.HANDYPAY_WEBHOOK_SECRET;
if (!SECRET) throw new Error("HANDYPAY_WEBHOOK_SECRET is not configured");

function hasValidSignature(payload: Buffer, signature: string): boolean {
  const prefix = "sha256=";
  if (!signature.startsWith(prefix)) return false;
  const hex = signature.slice(prefix.length);
  if (!/^[0-9a-f]{64}$/i.test(hex)) return false;
  const expected = createHmac("sha256", SECRET).update(payload).digest();
  const received = Buffer.from(hex, "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}

router.post(
  "/handypay",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = String(req.headers["x-handypay-signature"] ?? "");
    if (!hasValidSignature(req.body, signature)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const event = JSON.parse(req.body.toString("utf8"));
    // Persist event.id before side effects so redeliveries are idempotent.
    console.log("Verified HandyPay event", event.id, event.type);
    return res.json({ received: true });
  }
);

export default router;
typescript

Format de la charge utile Webhook

JSON
{
  "id": "evt_abc123",
  "type": "payment_intent.succeeded",
  "created": 1706745600,
  "data": { ... }
}
json

Compte

Lisez les frais et la disponibilité de paiement du marchand connecté plus le compte bancaire de paiement par défaut. Les numéros de banque et de routage sont masqués; seuls les quatre derniers chiffres sont retournés.

MéthodeVoieDésignation des marchandises
GET/v1/accountObtenez des détails de compte connecté et de paiement masqué
TypeScript
const account = await handypay("/account");

console.log({
  chargesEnabled: account.chargesEnabled,
  payoutsEnabled: account.payoutsEnabled,
  bank: account.bankAccount?.bankName,
  last4: account.bankAccount?.last4,
});
typescript

Codes d'erreur

CodeHTTPDésignation des marchandises
unauthorized401Clé API manquante ou invalide
key_revoked401API clé a été révoqué
key_expired401La clé API a expiré
rate_limit_exceeded429Trop de demandes
validation_error400Demande de validation du corps échoué
invalid_url400success_url ou cancel_url non valide
invalid_interval400Intervalle de facturation non pris en charge
product_not_found404Produit inexistant
customer_not_found404Le client n'existe pas
session_not_found404La session de vérification n'existe pas
payment_not_found404Le paiement est manquant ou n'est pas la propriété de ce marchand
subscription_not_found404L'abonnement n'existe pas
refund_not_allowed400Le paiement est contesté et ne peut être remboursé
already_refunded400Le paiement est déjà intégralement remboursé
refund_amount_too_large400Montant supérieur au solde remboursable restant
insufficient_balance400Le solde disponible ne peut pas couvrir la restitution
balance_verification_failed400La propriété ou la disponibilité du solde n'a pas pu être vérifiée
endpoint_not_found404Paramètres de API inconnus
stripe_error502Stripe API a retourné une erreur
internal_error500Erreur inattendue du serveur
payload_too_large413Organisme de demande supérieur à 1 Mo
prohibited_content400Le contenu viole la politique d'utilisation acceptable
webhook_url_must_be_https400Webhook URL doit utiliser HTTPS
key_creation_rate_exceeded429Trop de clés créées dans la fenêtre de temps
max_keys_reached400Les clés API actives maximales atteintes (25)

Sécurité

  • Gardez hp_live_ et hp_test_ les clés sur votre serveur. Ne les placez jamais dans le navigateur JavaScript, applications mobiles, URL, journaux ou contrôle de source.
  • Toutes les réponses comprennent : X-Content-Type-Options: nosniff, X-Frame-Options: DENYet Strict-Transport-Security En-têtes.
  • Requête de la limite de corps : 1 Mio, y compris les demandes en streaming ou en morceaux.
  • Les paramètres Webhook doivent utiliser HTTPS.
  • Vérifier les signatures du webhook contre les octets bruts exacts et comparer les digests en temps constant.
  • Les noms et descriptions de produits sont examinés à l'aide d'une liste de blocs de contenu interdite.
  • 5+ violations de contenu dans 24 heures suspendra vos clés API.

Limites des clés API

  • Max 3 clés créées par fenêtre de 10 minutes.
  • Max 10 clés créées par fenêtre d'une heure.
  • Max 25 clés actives par marchand.