//native
export type DynSelect_converted_status = string
// Dropdown of the org's "converted" lead statuses (LeadStatus where IsConverted = true).
export async function converted_status(auth: RT.Salesforce) {
const apiVersion = auth.api_version || "v60.0"
const url = new URL(`${auth.instance_url}/services/data/${apiVersion}/query`)
url.searchParams.append(
"q",
"SELECT MasterLabel FROM LeadStatus WHERE IsConverted = true"
)
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${auth.token}`,
Accept: "application/json",
},
})
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`)
}
const { records } = (await response.json()) as {
records: { MasterLabel: string }[]
}
return records.map((r) => ({ value: r.MasterLabel, label: r.MasterLabel }))
}
function xmlEscape(s: string) {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'")
}
/**
* Convert Lead
* Convert a lead into an account, contact, and (optionally) an opportunity. Salesforce has no REST endpoint for this, so it uses the SOAP convertLead call with the same OAuth token. converted_status must be a lead status marked as converted.
*/
export async function main(
auth: RT.Salesforce,
lead_id: string,
converted_status: DynSelect_converted_status,
options:
| {
accountId?: string
contactId?: string
opportunityName?: string
doNotCreateOpportunity?: boolean
ownerId?: string
overwriteLeadSource?: boolean
sendNotificationEmail?: boolean
}
| undefined
) {
const soapVersion = (auth.api_version || "v60.0").replace(/^v/, "")
const o = options ?? {}
const optional = [
o.accountId !== undefined
? `<urn:accountId>${xmlEscape(o.accountId)}</urn:accountId>`
: "",
o.contactId !== undefined
? `<urn:contactId>${xmlEscape(o.contactId)}</urn:contactId>`
: "",
o.opportunityName !== undefined
? `<urn:opportunityName>${xmlEscape(o.opportunityName)}</urn:opportunityName>`
: "",
o.doNotCreateOpportunity !== undefined
? `<urn:doNotCreateOpportunity>${o.doNotCreateOpportunity}</urn:doNotCreateOpportunity>`
: "",
o.ownerId !== undefined
? `<urn:ownerId>${xmlEscape(o.ownerId)}</urn:ownerId>`
: "",
o.overwriteLeadSource !== undefined
? `<urn:overwriteLeadSource>${o.overwriteLeadSource}</urn:overwriteLeadSource>`
: "",
o.sendNotificationEmail !== undefined
? `<urn:sendNotificationEmail>${o.sendNotificationEmail}</urn:sendNotificationEmail>`
: "",
].join("")
const envelope = `<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com">
<soapenv:Header><urn:SessionHeader><urn:sessionId>${auth.token}</urn:sessionId></urn:SessionHeader></soapenv:Header>
<soapenv:Body><urn:convertLead><urn:leadConverts>
<urn:leadId>${xmlEscape(lead_id)}</urn:leadId>
<urn:convertedStatus>${xmlEscape(converted_status)}</urn:convertedStatus>
${optional}
</urn:leadConverts></urn:convertLead></soapenv:Body>
</soapenv:Envelope>`
const response = await fetch(
`${auth.instance_url}/services/Soap/u/${soapVersion}`,
{
method: "POST",
headers: { "Content-Type": "text/xml; charset=UTF-8", SOAPAction: '""' },
body: envelope,
}
)
const text = await response.text()
if (!response.ok) {
throw new Error(`${response.status} ${text}`)
}
const pick = (tag: string) =>
text.match(new RegExp(`<(?:\\w+:)?${tag}>(.*?)</(?:\\w+:)?${tag}>`))?.[1]
if (pick("success") !== "true") {
const message =
text.match(/<faultstring>(.*?)<\/faultstring>/)?.[1] ??
text.match(
/<(?:\w+:)?errors>[\s\S]*?<(?:\w+:)?message>(.*?)<\/(?:\w+:)?message>/
)?.[1] ??
text
throw new Error(`convertLead failed: ${message}`)
}
return {
success: true,
leadId: pick("leadId"),
accountId: pick("accountId"),
contactId: pick("contactId"),
opportunityId: pick("opportunityId"),
}
}
Submitted by hugo989 9 days ago