func (s *Service) ActionStop(piID string, cErr error) error {
	tPayload, err := s.getTransitionalPayload(piID)
	if err != nil {
		return fmt.Errorf("get transitional payload: %w", err)
	}
	md := &MetaData{
		transitionalPayload: *tPayload,
		state:               stateFromName(tPayload.StateName),
		msgClone:            tPayload.Msg.Clone(),
		inbound:             true,
		properties:          map[string]interface{}{},
	}
	if err := s.deleteTransitionalPayload(md.PIID); err != nil {
		return fmt.Errorf("delete transitional payload: %w", err)
	}
	if cErr == nil {
		cErr = errProtocolStopped
	}
	md.err = customError{error: cErr}
	s.processCallback(md)
	return nil
}
ActionStop allows stopping the action by the piID.
package issuecredential
import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"github.com/hyperledger/aries-framework-go/pkg/common/log"
	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
	"github.com/hyperledger/aries-framework-go/spi/storage"
)
const (
	Name = "issue-credential"
	Spec = "https://didcomm.org/issue-credential/2.0/"
	ProposeCredentialMsgType = Spec + "propose-credential"
	OfferCredentialMsgType = Spec + "offer-credential"
	RequestCredentialMsgType = Spec + "request-credential"
	IssueCredentialMsgType = Spec + "issue-credential"
	AckMsgType = Spec + "ack"
	ProblemReportMsgType = Spec + "problem-report"
	CredentialPreviewMsgType = Spec + "credential-preview"
)
const (
	stateNameKey           = "state_name_"
	transitionalPayloadKey = "transitionalPayload_%s"
)
var (
	logger         = log.New("aries-framework/issuecredential/service")
	initialHandler = HandlerFunc(func(_ Metadata) error {
		return nil
	})
	errProtocolStopped = errors.New("protocol was stopped")
)
type customError struct{ error }
type transitionalPayload struct {
	Action
	StateName string
}
type MetaData struct {
	transitionalPayload
	state           state
	msgClone        service.DIDCommMsg
	inbound         bool
	properties      map[string]interface{}
	credentialNames []string
	offerCredential   *OfferCredential
	proposeCredential *ProposeCredential
	requestCredential *RequestCredential
	issueCredential   *IssueCredential
	err error
}
func (md *MetaData) Message() service.DIDCommMsg {
	return md.msgClone
}
func (md *MetaData) OfferCredential() *OfferCredential {
	return md.offerCredential
}
func (md *MetaData) ProposeCredential() *ProposeCredential {
	return md.proposeCredential
}
func (md *MetaData) RequestCredential() *RequestCredential {
	return md.requestCredential
}
func (md *MetaData) IssueCredential() *IssueCredential {
	return md.issueCredential
}
func (md *MetaData) CredentialNames() []string {
	return md.credentialNames
}
func (md *MetaData) StateName() string {
	return md.state.Name()
}
func (md *MetaData) Properties() map[string]interface{} {
	return md.properties
}
type Action struct {
	PIID     string
	Msg      service.DIDCommMsgMap
	MyDID    string
	TheirDID string
}
type Opt func(md *MetaData)
func WithProposeCredential(msg *ProposeCredential) Opt {
	return func(md *MetaData) {
		md.proposeCredential = msg
	}
}
func WithRequestCredential(msg *RequestCredential) Opt {
	return func(md *MetaData) {
		md.requestCredential = msg
	}
}
func WithOfferCredential(msg *OfferCredential) Opt {
	return func(md *MetaData) {
		md.offerCredential = msg
	}
}
func WithIssueCredential(msg *IssueCredential) Opt {
	return func(md *MetaData) {
		md.issueCredential = msg
	}
}
func WithFriendlyNames(names ...string) Opt {
	return func(md *MetaData) {
		md.credentialNames = names
	}
}
type Provider interface {
	Messenger() service.Messenger
	StorageProvider() storage.Provider
}
type Service struct {
	service.Action
	service.Message
	store      storage.Store
	callbacks  chan *MetaData
	messenger  service.Messenger
	middleware Handler
}
func New(p Provider) (*Service, error) {
	store, err := p.StorageProvider().OpenStore(Name)
	if err != nil {
		return nil, err
	}
	err = p.StorageProvider().SetStoreConfig(Name, storage.StoreConfiguration{TagNames: []string{transitionalPayloadKey}})
	if err != nil {
		return nil, fmt.Errorf("failed to set store config: %w", err)
	}
	svc := &Service{
		messenger:  p.Messenger(),
		store:      store,
		callbacks:  make(chan *MetaData),
		middleware: initialHandler,
	}
	go svc.startInternalListener()
	return svc, nil
}
func (s *Service) Use(items ...Middleware) {
	var handler Handler = initialHandler
	for i := len(items) - 1; i >= 0; i-- {
		handler = items[i](handler)
	}
	s.middleware = handler
}
func (s *Service) AddMiddleware(mw ...Middleware) {
	for i := len(mw) - 1; i >= 0; i-- {
		s.middleware = mw[i](s.middleware)
	}
}
func (s *Service) HandleInbound(msg service.DIDCommMsg, ctx service.DIDCommContext) (string, error) {
	logger.Debugf("handling inbound: %+v", msg)
	aEvent := s.ActionEvent()
	if aEvent == nil {
		return "", errors.New("no clients are registered to handle the message")
	}
	md, err := s.doHandle(msg, false)
	if err != nil {
		return "", fmt.Errorf("doHandle: %w", err)
	}
	md.inbound = true
	md.MyDID = ctx.MyDID()
	md.TheirDID = ctx.TheirDID()
	if canTriggerActionEvents(msg) {
		err = s.saveTransitionalPayload(md.PIID, md.transitionalPayload)
		if err != nil {
			return "", fmt.Errorf("save transitional payload: %w", err)
		}
		aEvent <- s.newDIDCommActionMsg(md)
		return "", nil
	}
	if err = s.handle(md); err != nil {
		return "", fmt.Errorf("handle inbound: %w", err)
	}
	return msg.ThreadID()
}
func (s *Service) HandleOutbound(msg service.DIDCommMsg, myDID, theirDID string) (string, error) {
	md, err := s.doHandle(msg, true)
	if err != nil {
		return "", fmt.Errorf("doHandle: %w", err)
	}
	md.MyDID = myDID
	md.TheirDID = theirDID
	if err = s.handle(md); err != nil {
		return "", fmt.Errorf("handle outbound: %w", err)
	}
	return msg.ThreadID()
}
func (s *Service) getCurrentStateNameAndPIID(msg service.DIDCommMsg) (string, string, error) {
	piID, err := getPIID(msg)
	if errors.Is(err, service.ErrThreadIDNotFound) {
		msg.SetID(uuid.New().String())
		return msg.ID(), stateNameStart, nil
	}
	if err != nil {
		return "", "", fmt.Errorf("piID: %w", err)
	}
	stateName, err := s.currentStateName(piID)
	if err != nil {
		return "", "", fmt.Errorf("currentStateName: %w", err)
	}
	return piID, stateName, nil
}
func (s *Service) doHandle(msg service.DIDCommMsg, outbound bool) (*MetaData, error) {
	piID, stateName, err := s.getCurrentStateNameAndPIID(msg)
	if err != nil {
		return nil, fmt.Errorf("getCurrentStateNameAndPIID: %w", err)
	}
	current := stateFromName(stateName)
	next, err := nextState(msg, outbound)
	if err != nil {
		return nil, fmt.Errorf("nextState: %w", err)
	}
	if !current.CanTransitionTo(next) {
		return nil, fmt.Errorf("invalid state transition: %s -> %s", current.Name(), next.Name())
	}
	return &MetaData{
		transitionalPayload: transitionalPayload{
			StateName: next.Name(),
			Action: Action{
				Msg:  msg.Clone(),
				PIID: piID,
			},
		},
		properties: map[string]interface{}{},
		state:      next,
		msgClone:   msg.Clone(),
	}, nil
}
func (s *Service) startInternalListener() {
	for msg := range s.callbacks {
		if msg.err == nil {
			msg.err = s.handle(msg)
		}
		if msg.err == nil {
			continue
		}
		logger.Errorf("abandoning: %s", msg.err)
		msg.state = &abandoning{Code: codeInternalError}
		if err := s.handle(msg); err != nil {
			logger.Errorf("listener handle: %s", err)
		}
	}
}
func isNoOp(s state) bool {
	_, ok := s.(*noOp)
	return ok
}
func (s *Service) handle(md *MetaData) error {
	var (
		current   = md.state
		actions   []stateAction
		stateName string
	)
	for !isNoOp(current) {
		stateName = current.Name()
		next, action, err := s.execute(current, md)
		if err != nil {
			return fmt.Errorf("execute: %w", err)
		}
		actions = append(actions, action)
		if !isNoOp(next) && !current.CanTransitionTo(next) {
			return fmt.Errorf("invalid state transition: %s --> %s", current.Name(), next.Name())
		}
		current = next
	}
	if err := s.saveStateName(md.PIID, stateName); err != nil {
		return fmt.Errorf("failed to persist state %s: %w", stateName, err)
	}
	for _, action := range actions {
		if err := action(s.messenger); err != nil {
			return fmt.Errorf("action %s: %w", stateName, err)
		}
	}
	return nil
}
func getPIID(msg service.DIDCommMsg) (string, error) {
	if pthID := msg.ParentThreadID(); pthID != "" {
		return pthID, nil
	}
	return msg.ThreadID()
}
func (s *Service) saveStateName(piID, stateName string) error {
	return s.store.Put(stateNameKey+piID, []byte(stateName))
}
func (s *Service) currentStateName(piID string) (string, error) {
	src, err := s.store.Get(stateNameKey + piID)
	if errors.Is(err, storage.ErrDataNotFound) {
		return stateNameStart, nil
	}
	return string(src), err
}
func stateFromName(name string) state {
	switch name {
	case stateNameStart:
		return &start{}
	case stateNameAbandoning:
		return &abandoning{}
	case stateNameDone:
		return &done{}
	case stateNameProposalReceived:
		return &proposalReceived{}
	case stateNameOfferSent:
		return &offerSent{}
	case stateNameRequestReceived:
		return &requestReceived{}
	case stateNameCredentialIssued:
		return &credentialIssued{}
	case stateNameProposalSent:
		return &proposalSent{}
	case stateNameOfferReceived:
		return &offerReceived{}
	case stateNameRequestSent:
		return &requestSent{}
	case stateNameCredentialReceived:
		return &credentialReceived{}
	default:
		return &noOp{}
	}
}
func nextState(msg service.DIDCommMsg, outbound bool) (state, error) {
	switch msg.Type() {
	case ProposeCredentialMsgType:
		if outbound {
			return &proposalSent{}, nil
		}
		return &proposalReceived{}, nil
	case OfferCredentialMsgType:
		if outbound {
			return &offerSent{}, nil
		}
		return &offerReceived{}, nil
	case RequestCredentialMsgType:
		if outbound {
			return &requestSent{}, nil
		}
		return &requestReceived{}, nil
	case IssueCredentialMsgType:
		return &credentialReceived{}, nil
	case ProblemReportMsgType:
		return &abandoning{}, nil
	case AckMsgType:
		return &done{}, nil
	default:
		return nil, fmt.Errorf("unrecognized msgType: %s", msg.Type())
	}
}
func (s *Service) saveTransitionalPayload(id string, data transitionalPayload) error {
	src, err := json.Marshal(data)
	if err != nil {
		return fmt.Errorf("marshal transitional payload: %w", err)
	}
	return s.store.Put(fmt.Sprintf(transitionalPayloadKey, id), src, storage.Tag{Name: transitionalPayloadKey})
}
func canTriggerActionEvents(msg service.DIDCommMsg) bool {
	return msg.Type() == ProposeCredentialMsgType ||
		msg.Type() == OfferCredentialMsgType ||
		msg.Type() == IssueCredentialMsgType ||
		msg.Type() == RequestCredentialMsgType ||
		msg.Type() == ProblemReportMsgType
}
func (s *Service) getTransitionalPayload(id string) (*transitionalPayload, error) {
	src, err := s.store.Get(fmt.Sprintf(transitionalPayloadKey, id))
	if err != nil {
		return nil, fmt.Errorf("store get: %w", err)
	}
	t := &transitionalPayload{}
	err = json.Unmarshal(src, t)
	if err != nil {
		return nil, fmt.Errorf("unmarshal transitional payload: %w", err)
	}
	return t, err
}
func (s *Service) deleteTransitionalPayload(id string) error {
	return s.store.Delete(fmt.Sprintf(transitionalPayloadKey, id))
}
func (s *Service) ActionContinue(piID string, opt Opt) error {
	tPayload, err := s.getTransitionalPayload(piID)
	if err != nil {
		return fmt.Errorf("get transitional payload: %w", err)
	}
	md := &MetaData{
		transitionalPayload: *tPayload,
		state:               stateFromName(tPayload.StateName),
		msgClone:            tPayload.Msg.Clone(),
		inbound:             true,
		properties:          map[string]interface{}{},
	}
	if opt != nil {
		opt(md)
	}
	if err := s.deleteTransitionalPayload(md.PIID); err != nil {
		return fmt.Errorf("delete transitional payload: %w", err)
	}
	s.processCallback(md)
	return nil
}