Skip to main content

Manual, Bulk, and External Orders

These order types have one thing in common: none of them go through a payment gateway at checkout time. No gateway call is made, and access is granted immediately.

Beyond that, they differ. Manual, Bulk, LTI, SAML, and totalDiscountCoupon orders have zero amount and never create payment rows. External orders are different: the sale happened outside the platform but the price is real, and PaymentSync creates a payment row after the order is approved.

This surprises developers who assume "no gateway call = no payment row."


Manual

An admin assigns a plan or issue to a user directly from the control panel. The platform creates an Order and a UserPlan, sets gateway_type = 'manual', and marks the order as approved immediately.

The activity log on the Order and UserPlan is the only audit trail.


Bulk

An admin uploads a CSV file to assign plans or issues to multiple users at once. Each row in the CSV produces an Order and a UserPlan with gateway_type = 'bulk'. Same result as Manual: approved immediately, no gateway, no payment row.


External

External is for sales that happened outside the platform (for example, a publisher who collects payment by wire transfer or invoice and then registers the assignment in Farfalla).

A plan is external when its type column equals external. There is no separate boolean column for this; any is_external flag exposed in code is just a derived view of type.

External plans require the create_external_plans feature flag on the tenant. Without the flag, the dashboard does not offer external plans and the order scope excludes them.

The order is created with gateway_type = 'external' and includes the price. No gateway call is made at checkout, and access is granted immediately.

Unlike Manual and Bulk, External plans carry a real price, and unlike all other non-gateway types, a payment row is created. PaymentSync (running hourly, registered as the dispatch-payment-sync artisan command) uses an external-payment strategy to detect approved external orders without payments and create the payment row using the order amount. This keeps the payment ledger complete for reporting and audit purposes.


LTI and SAML

LTI and SAML follow the same pattern: automatic assignment triggered by an authentication event. A user authenticates via an institutional identity provider, the platform creates an Order and UserPlan with gateway_type = 'lti' or 'saml', and access is granted immediately.


Total discount coupons

When a 100% discount coupon is applied, the order amount becomes zero. The platform sets gateway_type = 'totalDiscountCoupon' and skips the gateway entirely.

Whether a payment row is created depends on the tenant's charge_total_discount_coupon flag. When enabled, PaymentSync writes a nominal payment row (a fixed $1.00 USD marker, regardless of the original plan price) so the order surfaces in revenue and ledger reports. When disabled (default), no payment row is created. Either way, no gateway is contacted and nothing is actually charged.


What these orders have in common

  • No payment gateway call at checkout
  • gateway_key is null or empty
  • Order is approved at creation (no webhook needed)
  • Activity log on the Order and UserPlan is the audit trail

Manual, Bulk, LTI, SAML: no payments row, ever.

External: a payments row is created after order approval by PaymentSync.

totalDiscountCoupon: a payment row is created only when the tenant has charge_total_discount_coupon enabled. Otherwise no row exists.

When writing queries or reports that depend on payment data, filter by gateway_type to exclude or include these types explicitly. External orders always have a payment row; totalDiscountCoupon orders may or may not have one depending on the tenant flag; the rest never do.


X

Graph View