diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index c2dc9ab29e..b60193bd2f 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -16,6 +16,7 @@ import type { SendCredentialProblemReportOptions, DeleteCredentialOptions, SendRevocationNotificationOptions, + DeclineCredentialOfferOptions, } from './CredentialsApiOptions' import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { CredentialFormatsFromProtocols } from './protocol/CredentialProtocolOptions' @@ -48,7 +49,7 @@ export interface CredentialsApi { // Offer Credential Methods offerCredential(options: OfferCredentialOptions): Promise acceptOffer(options: AcceptCredentialOfferOptions): Promise - declineOffer(credentialRecordId: string): Promise + declineOffer(credentialRecordId: string, options?: DeclineCredentialOfferOptions): Promise negotiateOffer(options: NegotiateCredentialOfferOptions): Promise // Request Credential Methods @@ -324,12 +325,22 @@ export class CredentialsApi implements Credent return credentialRecord } - public async declineOffer(credentialRecordId: string): Promise { + public async declineOffer( + credentialRecordId: string, + options?: DeclineCredentialOfferOptions + ): Promise { const credentialRecord = await this.getById(credentialRecordId) credentialRecord.assertState(CredentialState.OfferReceived) // with version we can get the Service const protocol = this.getProtocol(credentialRecord.protocolVersion) + if (options?.sendProblemReport) { + await this.sendProblemReport({ + credentialRecordId, + description: options.problemReportDescription ?? 'Offer declined', + }) + } + await protocol.updateState(this.agentContext, credentialRecord, CredentialState.Declined) return credentialRecord @@ -532,29 +543,40 @@ export class CredentialsApi implements Credent /** * Send problem report message for a credential record * @param credentialRecordId The id of the credential record for which to send problem report - * @param message message to send * @returns credential record associated with the credential problem report message */ public async sendProblemReport(options: SendCredentialProblemReportOptions) { const credentialRecord = await this.getById(options.credentialRecordId) - if (!credentialRecord.connectionId) { - throw new CredoError(`No connectionId found for credential record '${credentialRecord.id}'.`) - } - const connectionRecord = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const protocol = this.getProtocol(credentialRecord.protocolVersion) - const { message } = await protocol.createProblemReport(this.agentContext, { + + const offerMessage = await protocol.findOfferMessage(this.agentContext, credentialRecord.id) + + const { message: problemReport } = await protocol.createProblemReport(this.agentContext, { description: options.description, credentialRecord, }) - message.setThread({ - threadId: credentialRecord.threadId, - parentThreadId: credentialRecord.parentThreadId, - }) + + // Use connection if present + const connectionRecord = credentialRecord.connectionId + ? await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + : undefined + connectionRecord?.assertReady() + + // If there's no connection (so connection-less, we require the state to be offer received) + if (!connectionRecord) { + credentialRecord.assertState(CredentialState.OfferReceived) + + if (!offerMessage) { + throw new CredoError(`No offer message found for credential record with id '${credentialRecord.id}'`) + } + } + const outboundMessageContext = await getOutboundMessageContext(this.agentContext, { - message, - associatedRecord: credentialRecord, + message: problemReport, connectionRecord, + associatedRecord: credentialRecord, + lastReceivedMessage: offerMessage ?? undefined, }) await this.messageSender.sendMessage(outboundMessageContext) diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 9f49b1ca98..a4cf6e82f2 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -139,3 +139,25 @@ export interface SendCredentialProblemReportOptions { credentialRecordId: string description: string } + +/** + * Interface for CredentialsApi.declineOffer. Decline a received credential offer and optionally send a problem-report message to Issuer. + */ +export interface DeclineCredentialOfferOptions { + // TODO: in next major release, move the id to this object as well + // for consistency with the proofs api + // credentialRecordId: string + + /** + * Whether to send a problem-report message to the issuer as part + * of declining the credential offer + */ + sendProblemReport?: boolean + + /** + * Description to include in the problem-report message + * Only used if `sendProblemReport` is set to `true`. + * @default "Offer declined" + */ + problemReportDescription?: string +} diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 20224a9ec7..a941bbc89e 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -323,7 +323,10 @@ export class ProofsApi implements ProofsApi { const protocol = this.getProtocol(proofRecord.protocolVersion) if (options.sendProblemReport) { - await this.sendProblemReport({ proofRecordId: options.proofRecordId, description: 'Request declined' }) + await this.sendProblemReport({ + proofRecordId: options.proofRecordId, + description: options.problemReportDescription ?? 'Request declined', + }) } await protocol.updateState(this.agentContext, proofRecord, ProofState.Declined) diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 0e9911febc..70d4a83f68 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -179,5 +179,17 @@ export interface SendProofProblemReportOptions { */ export interface DeclineProofRequestOptions { proofRecordId: string + + /** + * Whether to send a problem-report message to the verifier as part + * of declining the proof request + */ sendProblemReport?: boolean + + /** + * Description to include in the problem-report message + * Only used if `sendProblemReport` is set to `true`. + * @default "Request declined" + */ + problemReportDescription?: string }