Migration guides

Migrating from Stripe

If you know Stripe, you already know most of Qualy. This guide maps each Stripe concept to its Qualy equivalent and shows the code side by side.


Concept map

StripeQualyNotes
CustomerContactNames go in a nested profile. Reference a contact by id or email.
Product / PriceServiceA course, a fee, an insurance product. Optional on one-off payments.
Payment LinkA public service + its price templateReusable link anyone can pay. See Payment links.
Checkout SessionPayment portalHosted page for cards, bank transfers and local methods.
PaymentIntentPayment intentSame idea, but it carries line items, a dueAt, and tax.
InvoicePayment intent with itemsNo separate invoice object. Tax invoices are generated from the payment.
SubscriptionSubscriptionGenerates a payment intent per period.
RefundRefundA refund intent against the original charge.
DisputeDispute—
Connect: connected accountPartnershipA school, agent or supplier you pay.
Connect: transfers / application_fee_amountPayment splitsDeclared up front; Qualy pays out after settlement.
metadatacustomVisible in the dashboard once a custom field with the same name exists.
Smart Retries / dunning emailsDunningReminders for overdue payments.
Stripe-Account headerX-TENANT-ID headerSee Multi-tenancy.

Authentication

Stripe uses a secret key as a Bearer token. Qualy uses an API key plus your tenant id:

# Stripe
curl https://api.stripe.com/v1/customers -u sk_live_...:

# Qualy
curl https://api.qualyhq.com/v1/contacts \
  -H 'Authorization: ApiKey your-api-key-here' \
  -H 'X-TENANT-ID: your-tenant-id-here'

Qualy takes JSON bodies, not form-encoded ones, and writes use POST .../create and POST .../{id}/update.


Customers → contacts

const customer = await stripe.customers.create({
  email: 'john.doe@example.com',
  name: 'John Doe',
  metadata: { crmId: 'A-1042' },
});

A Stripe Payment Link is reusable and anyone can pay it. Qualy has both kinds:

  • Reusable, for anyone — like Stripe: a service (the Product) marked public with displayEcommerce: true, and a template (the Price). The link is https://{subdomain}.qualyhqportal.com/services/{serviceId}?action=buy-now. See Payment links.
  • One-off, for one student — every payment intent has its own link. Use it when you already know who owes what.

Side by side for a reusable link:

const product = await stripe.products.create({ name: 'Application fee' });
const price = await stripe.prices.create({
  product: product.id,
  currency: 'aud',
  unit_amount: 25000,
});
const link = await stripe.paymentLinks.create({
  line_items: [{ price: price.id, quantity: 1 }],
});
console.log(link.url);

When someone pays, Qualy creates the contact, an order and the payment for you, and sends the usual webhooks. For a one-off link, create a payment intent and send its links.short instead. Either way the student picks how to pay (card, bank transfer, PIX, PayID and more, depending on their country and currency) and Qualy confirms the payment by webhook.

Pay-what-you-owe and plans

Stripe's "customer chooses price" is a template with category: "payment-ad-hoc-variable-amount". A template can also hold several payments (a deposit and instalments) and splits, so one link can pay a school and keep your commission.


Checkout and PaymentIntents

Stripe Checkout → the payment portal. Redirect to links.long (or links.short) instead of session.url. Card payments always go through the portal, as Checkout does.

Stripe PaymentIntent + Elements → the payment intent + sign. For non-card methods you can build your own UI: get the methods available to the student, then sign the chosen one to get a QR code, a boleto, a PayID or bank details. See Collecting payments.

Statuses map roughly as follows:

Stripe PaymentIntentQualy payment intent
requires_payment_methoddue
processingprocessing
succeededpaid-full
—paid-partial (Qualy accepts partial payments)
—overdue (past dueAt)
canceledcanceled
refunded (on the charge)refunded-partial / refunded-full

Subscriptions

Stripe bills a Price on an interval. Qualy bills an amount on a schedule and creates a payment intent each period:

await stripe.subscriptions.create({
  customer: customer.id,
  items: [{ price: 'price_monthly_support' }],
  billing_cycle_anchor_config: { day_of_month: 1 },
});

For a fixed number of installments (a course paid in 6 parts) use an order with installment payments, or offer financing.


Refunds

await stripe.refunds.create({ payment_intent: 'pi_...', amount: 150000 });

Unlike Stripe, you reference the charge transaction, can preview fees and FX first, and the refund may wait for approval. See Issuing refunds for options, previews and statuses.


Connect → partnerships and splits

With Connect you collect on the platform and move money with transfer_data, application_fee_amount or separate Transfers. In Qualy you declare up front who gets what with payment splits, and Qualy pays each partner when the payment settles.

Stripe Connect patternQualy
Destination charge with application_fee_amountA keep-percentage split: the partner gets the payment, you keep your commission.
Separate charges and transfersOne split per recipient: fixed, percentage-on-amount or percentage-on-splits.
on_behalf_of a single sellersuppliers on the payment intent: everything goes to the supplier unless a split says otherwise.

You can see the payouts before you create the payment with POST /v1/payment-splits/preview/draft.


Webhooks

Stripe eventQualy event
customer.created / customer.updatedcontacts.create / contacts.update
payment_intent.createdpaymentIntents.create
payment_intent.succeeded, invoice.paidpaymentIntents.update with status: "paid-full"
charge.succeeded / charge.refundedtransactions.create / transactions.update
transfer.createdsplitIntents.create / splitIntents.update

Qualy sends fewer, broader events: watch paymentIntents.update and read status rather than subscribing to one event per state. See Webhook events.


Things that work differently

  • Every payment belongs to a contact. Reusable links create the contact at checkout; over the API, create the contact first (or pass an email).
  • Partial payments are normal. A student can pay a deposit now and the rest later against the same payment.
  • Students pay in their own currency. Set settlementCurrency, or let the student choose on the portal. See Collecting payments.
  • Saved cards can't be imported. Students enter their card again on their first Qualy payment.
Previous
Migrating to Qualy