Skip to content

Diagram viewer

Scroll to zoom. Drag to pan.

Team seats

The paying customer and the workspace receiving capacity can be different subjects. Your application still owns workspace membership, invitations, and RBAC.

import {
Access,
Billing,
Offer,
Purchase,
Seats,
assignSeat,
checkSeatAssignmentAccess,
createCheckout,
processPaymentNotification,
registerOffers,
unassignSeat,
} from "@byfungsi/sapa";
const teamAccess =
yield *
Access.entitlements({
entitlements: ["workspace.use"],
duration: "purchased_period",
});
const teamCapacity =
yield *
Seats.capacity({
pool: "workspace.members",
quantity: "purchase_quantity",
entitlement: "workspace.use",
});
const teamPurchase =
yield *
Purchase.parse({
key: "workspace.team.access",
revision: 1,
provides: [teamAccess, teamCapacity],
});
const teamBilling =
yield *
Billing.perSeatSubscription({
family: "workspace",
interval: "month",
price: { currency: "IDR", amount: 75_000n },
minimumQuantity: 1,
maximumQuantity: 100,
});
const team =
yield *
Offer.parse({
key: "workspace.team",
revision: 1,
purchase: teamPurchase,
billing: teamBilling,
});
yield * registerOffers([team]);
yield *
createCheckout({
type: "offer",
offerKey: team.key,
offerRevision: team.revision,
quantity: 10,
beneficiary: { type: "merchant_subject", subjectId: "workspace-42" },
provider: "midtrans",
customer: {
externalId: "billing-customer-42",
email: "owner@example.com",
name: "Workspace Owner",
},
successUrl,
cancelUrl,
clientIp: null,
idempotencyKey: "workspace-42-seats-10",
});
yield *
processPaymentNotification({
provider: "midtrans",
payload: rawProviderPayload,
});
const assignment =
yield *
assignSeat({
idempotencyKey: "workspace-42-member-7",
beneficiary: { type: "merchant_subject", subjectId: "workspace-42" },
pool: "workspace.members",
memberId: "member-7",
principal: "workspace-admin-3",
});
const access =
yield *
checkSeatAssignmentAccess({
beneficiary: { type: "merchant_subject", subjectId: "workspace-42" },
pool: "workspace.members",
memberId: "member-7",
entitlement: "workspace.use",
});
yield *
unassignSeat({
idempotencyKey: "workspace-42-member-7-remove",
assignmentId: assignment.assignmentId,
principal: "workspace-admin-3",
});

Authenticate and authorize the principal in your host before calling SAPA. Assignments are capacity accounting, not identity proof. To reduce quantity, first unassign enough members, then schedule a guarded next-period quantity change and settle the renewal. SAPA rejects a reduction below active occupancy.