Phase8: Auto bill credit card
All checks were successful
Build and Push / build (push) Successful in 1m45s
All checks were successful
Build and Push / build (push) Successful in 1m45s
This commit is contained in:
@@ -105,6 +105,14 @@ const MIGRATION_SQL = `
|
||||
ON tenant_requests(setup_invoice_id)
|
||||
WHERE setup_invoice_id IS NOT NULL;
|
||||
|
||||
-- Phase 9b: optional initial channel-user ids per channel package
|
||||
-- collected during onboarding. JSONB so the shape can vary by
|
||||
-- channel (today it's a string[] per channel id, matching
|
||||
-- PiecedTenantSpec.channelUsers). Default '{}' so reads on legacy
|
||||
-- rows return an empty object rather than null.
|
||||
ALTER TABLE tenant_requests
|
||||
ADD COLUMN IF NOT EXISTS channel_users JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
-- Feature 6: free-form customer note attached to the request.
|
||||
-- Currently surfaced only by resume requests (where the customer
|
||||
-- explains why they want reactivation), but the column is generic
|
||||
@@ -896,8 +904,8 @@ export async function createTenantRequest(
|
||||
(zitadel_org_id, zitadel_user_id, company_name, instance_name,
|
||||
contact_name, contact_email, agent_name, soul_md, agents_md,
|
||||
packages, billing_address, billing_notes, encrypted_secrets,
|
||||
is_personal)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
is_personal, channel_users)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15::jsonb)
|
||||
RETURNING *`,
|
||||
[
|
||||
params.zitadelOrgId,
|
||||
@@ -914,6 +922,7 @@ export async function createTenantRequest(
|
||||
params.billingNotes,
|
||||
params.encryptedSecrets ?? null,
|
||||
params.isPersonal ?? false,
|
||||
JSON.stringify(params.channelUsers ?? {}),
|
||||
]
|
||||
);
|
||||
return mapRow(result.rows[0]);
|
||||
@@ -1449,6 +1458,7 @@ function mapRow(row: any): TenantRequest {
|
||||
adminNotes: row.admin_notes,
|
||||
tenantName: row.tenant_name,
|
||||
setupInvoiceId: row.setup_invoice_id ?? null,
|
||||
channelUsers: (row.channel_users ?? {}) as Record<string, string[]>,
|
||||
encryptedSecrets: row.encrypted_secrets ?? null,
|
||||
isPersonal: row.is_personal ?? false,
|
||||
dismissedAt:
|
||||
@@ -4235,6 +4245,7 @@ export async function createTenantRequestPendingPayment(params: {
|
||||
billingNotes?: string;
|
||||
encryptedSecrets?: Buffer | null;
|
||||
isPersonal: boolean;
|
||||
channelUsers?: Record<string, string[]>;
|
||||
}): Promise<TenantRequest> {
|
||||
await ensureSchema();
|
||||
const result = await getPool().query(
|
||||
@@ -4244,10 +4255,11 @@ export async function createTenantRequestPendingPayment(params: {
|
||||
agent_name, soul_md, agents_md, packages,
|
||||
billing_address, billing_notes,
|
||||
encrypted_secrets, is_personal,
|
||||
channel_users,
|
||||
status, request_type
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb, $12,
|
||||
$13, $14, 'pending_payment', 'provision'
|
||||
$13, $14, $15::jsonb, 'pending_payment', 'provision'
|
||||
)
|
||||
RETURNING *`,
|
||||
[
|
||||
@@ -4265,6 +4277,7 @@ export async function createTenantRequestPendingPayment(params: {
|
||||
params.billingNotes ?? null,
|
||||
params.encryptedSecrets ?? null,
|
||||
params.isPersonal,
|
||||
JSON.stringify(params.channelUsers ?? {}),
|
||||
]
|
||||
);
|
||||
return mapRow(result.rows[0]);
|
||||
|
||||
@@ -84,6 +84,21 @@ export interface PackageDef {
|
||||
* stays opt-in.
|
||||
*/
|
||||
recommended?: boolean;
|
||||
/**
|
||||
* Phase 9b: when true, the onboarding wizard collects the
|
||||
* customer's own user id for this channel (e.g. their Telegram
|
||||
* numeric id, their Threema ID) at request time. The collected
|
||||
* id is forwarded with the tenant request, stored on the row,
|
||||
* and applied on admin approval:
|
||||
* - spec.channelUsers[<channel>] gets the id seeded so the
|
||||
* operator's first reconcile already has it
|
||||
* - for Threema specifically, the approve handler additionally
|
||||
* calls the relay's createRoute() so inbound messages from
|
||||
* that id reach the new tenant
|
||||
* Customers can add more ids later via the channel-users page.
|
||||
* Help copy and label come from channelUsers.<id>IdHelp.
|
||||
*/
|
||||
collectsChannelUserId?: boolean;
|
||||
}
|
||||
|
||||
export const PACKAGE_CATALOG: PackageDef[] = [
|
||||
@@ -137,6 +152,7 @@ export const PACKAGE_CATALOG: PackageDef[] = [
|
||||
instructionsKey: "packages.telegram.instructions",
|
||||
disclaimerKey: "packages.telegram.disclaimer",
|
||||
category: "channel",
|
||||
collectsChannelUserId: true,
|
||||
},
|
||||
{
|
||||
id: "discord",
|
||||
@@ -166,6 +182,7 @@ export const PACKAGE_CATALOG: PackageDef[] = [
|
||||
instructionsKey: "packages.discord.instructions",
|
||||
disclaimerKey: "packages.discord.disclaimer",
|
||||
category: "channel",
|
||||
collectsChannelUserId: true,
|
||||
},
|
||||
{
|
||||
id: "threema",
|
||||
@@ -182,6 +199,7 @@ export const PACKAGE_CATALOG: PackageDef[] = [
|
||||
disclaimerKey: "packages.threema.disclaimer",
|
||||
category: "channel",
|
||||
recommended: true,
|
||||
collectsChannelUserId: true,
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -152,6 +152,12 @@ export const onboardingSchema = z.object({
|
||||
packageSecrets: z
|
||||
.record(z.string(), z.record(z.string(), z.string()))
|
||||
.optional(),
|
||||
// Phase 9b: per-channel initial user ids collected during
|
||||
// onboarding. Map of channel package id → list of user ids the
|
||||
// customer wants to authorize. Applied at admin approval time.
|
||||
channelUsers: z
|
||||
.record(z.string(), z.array(z.string().trim().min(1).max(200)))
|
||||
.optional(),
|
||||
billingAddress: billingAddressSchema.optional(),
|
||||
billingNotes: z.string().max(2_000).optional(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user