func (i *Invoice) Process(ctx context.Context) error {
	return i.GetStateController().Process(i)
}
Process delegates the action to its state controller
package invoice
import (
	"context"
	"encoding/json"
	"time"
	"gorm.io/gorm"
	"github.com/imrenagi/go-payment"
	"github.com/imrenagi/go-payment/config"
	"github.com/google/uuid"
)
func newInvoiceNumber() string {
	return uuid.New().String()
}
func NewDefault() *Invoice {
	now := time.Now()
	inv := New(now, now.AddDate(0, 0, 1))
	return inv
}
func New(invoiceDate, dueDate time.Time) *Invoice {
	return &Invoice{
		Number:          newInvoiceNumber(),
		InvoiceDate:     invoiceDate,
		DueDate:         dueDate,
		Currency:        "IDR",
		State:           Draft,
		StateController: &DraftState{},
	}
}
type Invoice struct {
	payment.Model
	Title           string          `json:"-"`
	Number          string          `json:"number" gorm:"unique_index:inv_number_k"`
	InvoiceDate     time.Time       `json:"invoice_date"`
	DueDate         time.Time       `json:"due_date"`
	PaidAt          *time.Time      `json:"paid_at"`
	Currency        string          `json:"-"`
	SubTotal        float64         `json:"-"`
	Discount        float64         `json:"-"`
	Tax             float64         `json:"-"`
	ServiceFee      float64         `json:"-"`
	InstallmentFee  float64         `json:"-"`
	State           State           `json:"-"`
	StateController StateController `json:"-" gorm:"-"`
	LineItems       []LineItem      `json:"items"`
	Payment         *Payment        `json:"payment" gorm:"ForeignKey:InvoiceID"`
	BillingAddress  *BillingAddress `json:"billing_address" gorm:"ForeignKey:InvoiceID"`
	SubscriptionID  *uint64         `json:"-" gorm:"sql:index;"`
}
func (i *Invoice) GetTitle() string {
	return i.Title
}
func (i *Invoice) MarshalJSON() ([]byte, error) {
	type Alias Invoice
	type value struct {
		Currency       string  `json:"currency"`
		Total          float64 `json:"total_amount"`
		SubTotal       float64 `json:"sub_total_amount"`
		Discount       float64 `json:"discount_amount"`
		Tax            float64 `json:"tax_amount"`
		ServiceFee     float64 `json:"admin_fee_amount"`
		InstallmentFee float64 `json:"installment_fee_amount"`
	}
	return json.Marshal(&struct {
		*Alias
		InvTitle string `json:"title"`
		State    string `json:"state"`
		Value    value  `json:"transaction_values"`
	}{
		Alias:    (*Alias)(i),
		InvTitle: i.GetTitle(),
		State:    i.GetState().String(),
		Value: value{
			Currency:       i.Currency,
			Total:          i.GetTotal(),
			SubTotal:       i.SubTotal,
			Discount:       i.Discount,
			Tax:            i.Tax,
			ServiceFee:     i.ServiceFee,
			InstallmentFee: i.InstallmentFee,
		},
	})
}
func (i *Invoice) GetTotal() float64 {
	return i.SubTotal + i.Tax + i.ServiceFee + i.InstallmentFee - i.Discount
}
type paymentMethodFinder interface {
	FindByPaymentType(ctx context.Context, paymentType payment.PaymentType, opts ...payment.Option) (config.FeeConfigReader, error)
}
func (i *Invoice) Clear() {
	i.SubTotal = 0
	i.Discount = 0
	i.Tax = 0
	i.ServiceFee = 0
	i.InstallmentFee = 0
	i.State = Draft
	i.LineItems = []LineItem{}
	i.Payment = nil
}
func (i *Invoice) AfterFind(tx *gorm.DB) error {
	i.StateController = NewState(i.State.String())
	return nil
}
func (i *Invoice) GetStateController() StateController {
	if i.StateController == nil {
		i.StateController = NewState(i.State.String())
	}
	return i.StateController
}
func (i *Invoice) UpsertBillingAddress(name, email, phoneNumber string) error {
	if i.BillingAddress == nil {
		addr, err := NewBillingAddress(name, email, phoneNumber)
		if err != nil {
			return err
		}
		i.BillingAddress = addr
	} else {
		err := i.BillingAddress.Update(name, email, phoneNumber)
		if err != nil {
			return err
		}
	}
	return nil
}
func (i *Invoice) UpdatePaymentMethod(ctx context.Context, payment *Payment, finder paymentMethodFinder, opts ...payment.Option) error {
	if payment == nil {
		return InvoiceError{InvoiceErrorPaymentMethodNotSet}
	}
	i.Payment = payment
	i.Payment.InvoiceID = i.ID
	feeCalculator, err := finder.FindByPaymentType(ctx, i.Payment.PaymentType, opts...)
	if err != nil {
		return err
	}
	i.ServiceFee = 0
	i.InstallmentFee = 0
	if f := feeCalculator.GetAdminFeeConfig(i.Currency); f != nil {
		i.ServiceFee = f.Estimate(i.SubTotal)
	}
	if f := feeCalculator.GetInstallmentFeeConfig(i.Currency); f != nil {
		i.InstallmentFee = f.Estimate(i.SubTotal)
	}
	return nil
}
func (i *Invoice) CreateChargeRequest(ctx context.Context, charger PaymentCharger) error {
	res, err := charger.Create(ctx, i)
	if err != nil {
		return err
	}
	if i.Payment != nil {
		i.Payment.Gateway = charger.Gateway().String()
		i.Payment.Token = res.PaymentToken
		i.Payment.RedirectURL = res.PaymentURL
		i.Payment.TransactionID = res.TransactionID
	}
	return nil
}
func (Invoice) TableName() string {
	return "invoices"
}
func (i *Invoice) SetItems(ctx context.Context, items []LineItem) error {
	i.LineItems = items
	i.SubTotal = i.GetSubTotal()
	return nil
}
func (i *Invoice) AddDiscount(value float64) error {
	if value < 0 {
		return InvoiceError{InvoiceErrorInvalidDiscountValue}
	}
	i.Discount = value
	return nil
}
func (i *Invoice) RemoveDiscount() error {
	i.Discount = 0
	return nil
}
func (i *Invoice) GetSubTotal() float64 {
	var sum float64
	for _, item := range i.LineItems {
		sum += item.SubTotal()
	}
	return sum
}
func (i *Invoice) SetState(state StateController) error {
	i.StateController = state
	i.State = state.State(i)
	return nil
}
func (i *Invoice) Publish(ctx context.Context) error {
	if i.Payment == nil {
		return InvoiceError{InvoiceErrorPaymentMethodNotSet}
	}
	if i.BillingAddress == nil {
		return InvoiceError{InvoiceErrorBillingAddressNotSet}
	}
	now := time.Now()
	due := now.AddDate(0, 0, 1)
	i.InvoiceDate = now
	i.DueDate = due
	return i.GetStateController().Publish(i)
}
func (i *Invoice) Pay(ctx context.Context, transactionID string) error {
	now := time.Now()
	i.PaidAt = &now
	return i.GetStateController().Pay(i, transactionID)
}