flamingo.me/flamingo-commerce/v3@v3.11.0/checkout/application/orderService.go (about)

     1  package application
     2  
     3  import (
     4  	"context"
     5  	"encoding/gob"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"go.opencensus.io/trace"
    10  
    11  	"flamingo.me/flamingo/v3/framework/flamingo"
    12  	"flamingo.me/flamingo/v3/framework/opencensus"
    13  	"flamingo.me/flamingo/v3/framework/web"
    14  	"go.opencensus.io/stats"
    15  	"go.opencensus.io/stats/view"
    16  
    17  	"flamingo.me/flamingo-commerce/v3/cart/application"
    18  	"flamingo.me/flamingo-commerce/v3/cart/domain/cart"
    19  	"flamingo.me/flamingo-commerce/v3/cart/domain/decorator"
    20  	"flamingo.me/flamingo-commerce/v3/cart/domain/placeorder"
    21  	paymentDomain "flamingo.me/flamingo-commerce/v3/payment/domain"
    22  	"flamingo.me/flamingo-commerce/v3/payment/interfaces"
    23  	priceDomain "flamingo.me/flamingo-commerce/v3/price/domain"
    24  )
    25  
    26  type (
    27  	// OrderService defines the order service
    28  	OrderService struct {
    29  		logger                 flamingo.Logger
    30  		cartService            *application.CartService
    31  		cartReceiverService    *application.CartReceiverService
    32  		deliveryInfoBuilder    cart.DeliveryInfoBuilder
    33  		webCartPaymentGateways map[string]interfaces.WebCartPaymentGateway
    34  		decoratedCartFactory   *decorator.DecoratedCartFactory
    35  	}
    36  
    37  	// PlaceOrderInfo struct defines the data of payments on placed orders
    38  	PlaceOrderInfo struct {
    39  		PaymentInfos []PlaceOrderPaymentInfo
    40  		PlacedOrders placeorder.PlacedOrderInfos
    41  		ContactEmail string
    42  		Cart         cart.Cart
    43  	}
    44  
    45  	// PlaceOrderPaymentInfo holding payment infos
    46  	PlaceOrderPaymentInfo struct {
    47  		Gateway         string
    48  		PaymentProvider string
    49  		Method          string
    50  		CreditCardInfo  *placeorder.CreditCardInfo
    51  		Amount          priceDomain.Price
    52  		Title           string
    53  	}
    54  )
    55  
    56  const (
    57  	// PaymentFlowStandardCorrelationID used as correlationid for the start of the payment (session scoped)
    58  	PaymentFlowStandardCorrelationID = "checkout"
    59  
    60  	// LastPlacedOrderSessionKey is the session key for storing the last placed order
    61  	LastPlacedOrderSessionKey = "orderservice_last_placed"
    62  )
    63  
    64  var (
    65  	// cartValidationFailCount counts validation failures on carts
    66  	cartValidationFailCount = stats.Int64("flamingo-commerce/checkout/orders/cart_validation_failed", "Count of failures while validating carts", stats.UnitDimensionless)
    67  
    68  	// noPaymentSelectionCount counts error for orders without payment selection
    69  	noPaymentSelectionCount = stats.Int64("flamingo-commerce/checkout/orders/no_payment_selection", "Count of carts without having a selected payment", stats.UnitDimensionless)
    70  
    71  	// paymentGatewayNotFoundCount counts errors if payment gateway for selected payment could not be found
    72  	paymentGatewayNotFoundCount = stats.Int64("flamingo-commerce/checkout/orders/payment_gateway_not_found", "The selected payment gateway could not be found", stats.UnitDimensionless)
    73  
    74  	// paymentFlowStatusErrorCount counts errors while fetching payment flow status
    75  	paymentFlowStatusErrorCount = stats.Int64("flamingo-commerce/checkout/orders/payment_flow_status_error", "Count of failures while fetching payment flow status", stats.UnitDimensionless)
    76  
    77  	// orderPaymentFromFlowErrorCount counts errors while fetching payment from flow
    78  	orderPaymentFromFlowErrorCount = stats.Int64("flamingo-commerce/checkout/orders/order_payment_from_flow_error", "Count of failures while fetching payment from flow", stats.UnitDimensionless)
    79  
    80  	// paymentFlowStatusFailedCanceledCount counts orders trying to be placed with payment status either failed or canceled
    81  	paymentFlowStatusFailedCanceledCount = stats.Int64("flamingo-commerce/checkout/orders/payment_flow_status_failed_canceled", "Count of payments with status failed or canceled", stats.UnitDimensionless)
    82  
    83  	// paymentFlowStatusAbortedCount counts orders trying to be placed with payment status aborted
    84  	paymentFlowStatusAbortedCount = stats.Int64("flamingo-commerce/checkout/orders/payment_flow_status_aborted", "Count of payments with status aborted", stats.UnitDimensionless)
    85  
    86  	// placeOrderFailCount counts failed placed orders
    87  	placeOrderFailCount = stats.Int64("flamingo-commerce/checkout/orders/place_order_failed", "Count of failures while placing orders", stats.UnitDimensionless)
    88  
    89  	// placeOrderSuccessCount counts successfully placed orders
    90  	placeOrderSuccessCount = stats.Int64("flamingo-commerce/checkout/orders/place_order_successful", "Count of successfully placed orders", stats.UnitDimensionless)
    91  )
    92  
    93  func init() {
    94  	gob.Register(PlaceOrderInfo{})
    95  	openCensusViews := map[string]*stats.Int64Measure{
    96  		"flamingo-commerce/checkout/orders/cart_validation_failed":              cartValidationFailCount,
    97  		"flamingo-commerce/checkout/orders/no_payment_selection":                noPaymentSelectionCount,
    98  		"flamingo-commerce/checkout/orders/payment_gateway_not_found":           paymentGatewayNotFoundCount,
    99  		"flamingo-commerce/checkout/orders/payment_flow_status_error":           paymentFlowStatusErrorCount,
   100  		"flamingo-commerce/checkout/orders/order_payment_from_flow_error":       orderPaymentFromFlowErrorCount,
   101  		"flamingo-commerce/checkout/orders/payment_flow_status_failed_canceled": paymentFlowStatusFailedCanceledCount,
   102  		"flamingo-commerce/checkout/orders/payment_flow_status_aborted":         paymentFlowStatusAbortedCount,
   103  		"flamingo-commerce/checkout/orders/place_order_failed":                  placeOrderFailCount,
   104  		"flamingo-commerce/checkout/orders/place_order_successful":              placeOrderSuccessCount,
   105  	}
   106  
   107  	for name, measure := range openCensusViews {
   108  		err := opencensus.View(name, measure, view.Sum())
   109  		if err != nil {
   110  			panic(err)
   111  		}
   112  
   113  		stats.Record(context.Background(), measure.M(0))
   114  	}
   115  }
   116  
   117  // Inject dependencies
   118  func (os *OrderService) Inject(
   119  	logger flamingo.Logger,
   120  	CartService *application.CartService,
   121  	CartReceiverService *application.CartReceiverService,
   122  	DeliveryInfoBuilder cart.DeliveryInfoBuilder,
   123  	webCartPaymentGatewayProvider interfaces.WebCartPaymentGatewayProvider,
   124  	decoratedCartFactory *decorator.DecoratedCartFactory,
   125  
   126  ) {
   127  	os.logger = logger.WithField(flamingo.LogKeyCategory, "checkout.OrderService").WithField(flamingo.LogKeyModule, "checkout")
   128  	os.cartService = CartService
   129  	os.cartReceiverService = CartReceiverService
   130  	os.webCartPaymentGateways = webCartPaymentGatewayProvider()
   131  	os.deliveryInfoBuilder = DeliveryInfoBuilder
   132  	os.decoratedCartFactory = decoratedCartFactory
   133  }
   134  
   135  // GetPaymentGateway tries to get the supplied payment gateway by code from the registered payment gateways
   136  func (os *OrderService) GetPaymentGateway(ctx context.Context, paymentGatewayCode string) (interfaces.WebCartPaymentGateway, error) {
   137  	_, span := trace.StartSpan(ctx, "checkout/OrderService/GetPaymentGateway")
   138  	defer span.End()
   139  
   140  	gateway, ok := os.webCartPaymentGateways[paymentGatewayCode]
   141  	if !ok {
   142  		return nil, errors.New("Payment gateway " + paymentGatewayCode + " not found")
   143  	}
   144  
   145  	return gateway, nil
   146  }
   147  
   148  // GetAvailablePaymentGateways returns the list of registered WebCartPaymentGateway
   149  func (os *OrderService) GetAvailablePaymentGateways(ctx context.Context) map[string]interfaces.WebCartPaymentGateway {
   150  	_, span := trace.StartSpan(ctx, "checkout/OrderService/GetAvailablePaymentGateways")
   151  	defer span.End()
   152  
   153  	return os.webCartPaymentGateways
   154  }
   155  
   156  // CurrentCartPlaceOrder places the current cart without additional payment processing
   157  func (os *OrderService) CurrentCartPlaceOrder(ctx context.Context, session *web.Session, cartPayment placeorder.Payment) (*PlaceOrderInfo, error) {
   158  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/CurrentCartPlaceOrder")
   159  	defer span.End()
   160  
   161  	var info *PlaceOrderInfo
   162  	var err error
   163  	web.RunWithDetachedContext(ctx, func(placeOrderContext context.Context) {
   164  		info, err = func() (*PlaceOrderInfo, error) {
   165  			decoratedCart, err := os.cartReceiverService.ViewDecoratedCart(placeOrderContext, session)
   166  			if err != nil {
   167  				os.logger.WithContext(placeOrderContext).Error("OnStepCurrentCartPlaceOrder GetDecoratedCart Error ", err)
   168  
   169  				return nil, err
   170  			}
   171  
   172  			return os.placeOrder(placeOrderContext, session, decoratedCart, cartPayment)
   173  		}()
   174  	})
   175  
   176  	return info, err
   177  }
   178  
   179  func (os *OrderService) placeOrder(ctx context.Context, session *web.Session, decoratedCart *decorator.DecoratedCart, payment placeorder.Payment) (*PlaceOrderInfo, error) {
   180  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/placeOrder")
   181  	defer span.End()
   182  
   183  	validationResult := os.cartService.ValidateCart(ctx, session, decoratedCart)
   184  	if !validationResult.IsValid() {
   185  		// record cartValidationFailCount metric
   186  		stats.Record(ctx, cartValidationFailCount.M(1))
   187  		os.logger.WithContext(ctx).Warn("Try to place an invalid cart")
   188  
   189  		return nil, errors.New("cart is invalid")
   190  	}
   191  
   192  	placedOrderInfos, err := os.cartService.PlaceOrderWithCart(ctx, session, &decoratedCart.Cart, &payment)
   193  	if err != nil {
   194  		// record placeOrderFailCount metric
   195  		stats.Record(ctx, placeOrderFailCount.M(1))
   196  		os.logger.WithContext(ctx).Error("Error during place Order:" + err.Error())
   197  
   198  		return nil, errors.New("error while placing the order. please contact customer support")
   199  	}
   200  
   201  	placeOrderInfo := os.preparePlaceOrderInfo(ctx, decoratedCart.Cart, placedOrderInfos, payment)
   202  	os.storeLastPlacedOrder(ctx, placeOrderInfo)
   203  
   204  	// record placeOrderSuccessCount metric
   205  	stats.Record(ctx, placeOrderSuccessCount.M(1))
   206  
   207  	return placeOrderInfo, nil
   208  }
   209  
   210  // CancelOrder cancels an previously placed order and returns the restored cart with the order content
   211  func (os *OrderService) CancelOrder(ctx context.Context, session *web.Session, order *PlaceOrderInfo) (*cart.Cart, error) {
   212  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/CancelOrder")
   213  	defer span.End()
   214  
   215  	return os.cartService.CancelOrder(ctx, session, order.PlacedOrders, order.Cart)
   216  }
   217  
   218  // CancelOrderWithoutRestore cancels an previously placed order
   219  func (os *OrderService) CancelOrderWithoutRestore(ctx context.Context, session *web.Session, order *PlaceOrderInfo) error {
   220  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/CancelOrderWithoutRestore")
   221  	defer span.End()
   222  
   223  	return os.cartService.CancelOrderWithoutRestore(ctx, session, order.PlacedOrders)
   224  }
   225  
   226  // CurrentCartPlaceOrderWithPaymentProcessing places the current cart which is fetched from the context
   227  func (os *OrderService) CurrentCartPlaceOrderWithPaymentProcessing(ctx context.Context, session *web.Session) (*PlaceOrderInfo, error) {
   228  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/CurrentCartPlaceOrderWithPaymentProcessing")
   229  	defer span.End()
   230  
   231  	var info *PlaceOrderInfo
   232  	var err error
   233  	// use a background context from here on to prevent the place order canceled by context cancel
   234  	web.RunWithDetachedContext(ctx, func(placeOrderContext context.Context) {
   235  		info, err = func() (*PlaceOrderInfo, error) {
   236  			// fetch decorated cart either via cache or freshly from cart receiver service
   237  			decoratedCart, err := os.cartReceiverService.ViewDecoratedCart(placeOrderContext, session)
   238  			if err != nil {
   239  				os.logger.WithContext(placeOrderContext).Warn("Cannot create decorated cart from cart")
   240  
   241  				return nil, errors.New("cart is invalid")
   242  			}
   243  
   244  			return os.placeOrderWithPaymentProcessing(placeOrderContext, decoratedCart, session)
   245  		}()
   246  	})
   247  
   248  	return info, err
   249  }
   250  
   251  // CartPlaceOrderWithPaymentProcessing places the cart passed to the function
   252  // this function enables clients to pass a cart as is, without the usage of the cartReceiverService
   253  func (os *OrderService) CartPlaceOrderWithPaymentProcessing(ctx context.Context, decoratedCart *decorator.DecoratedCart,
   254  	session *web.Session) (*PlaceOrderInfo, error) {
   255  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/CartPlaceOrderWithPaymentProcessing")
   256  	defer span.End()
   257  
   258  	var info *PlaceOrderInfo
   259  	var err error
   260  	// use a background context from here on to prevent the place order canceled by context cancel
   261  	web.RunWithDetachedContext(ctx, func(placeOrderContext context.Context) {
   262  		info, err = os.placeOrderWithPaymentProcessing(placeOrderContext, decoratedCart, session)
   263  	})
   264  
   265  	return info, err
   266  }
   267  
   268  // CartPlaceOrder places the cart passed to the function
   269  // this function enables clients to pass a cart as is, without the usage of the cartReceiverService
   270  func (os *OrderService) CartPlaceOrder(ctx context.Context, decoratedCart *decorator.DecoratedCart, payment placeorder.Payment) (*PlaceOrderInfo, error) {
   271  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/CartPlaceOrder")
   272  	defer span.End()
   273  
   274  	var info *PlaceOrderInfo
   275  	var err error
   276  	web.RunWithDetachedContext(ctx, func(placeOrderContext context.Context) {
   277  		info, err = os.placeOrder(placeOrderContext, web.SessionFromContext(ctx), decoratedCart, payment)
   278  	})
   279  
   280  	return info, err
   281  }
   282  
   283  // storeLastPlacedOrder stores the last placed order/cart in the session
   284  func (os *OrderService) storeLastPlacedOrder(ctx context.Context, info *PlaceOrderInfo) {
   285  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/storeLastPlacedOrder")
   286  	defer span.End()
   287  
   288  	session := web.SessionFromContext(ctx)
   289  
   290  	_ = session.Store(LastPlacedOrderSessionKey, info)
   291  }
   292  
   293  // LastPlacedOrder returns the last placed order/cart if available
   294  func (os *OrderService) LastPlacedOrder(ctx context.Context) (*PlaceOrderInfo, error) {
   295  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/LastPlacedOrder")
   296  	defer span.End()
   297  
   298  	session := web.SessionFromContext(ctx)
   299  
   300  	lastPlacedOrder, found := session.Load(LastPlacedOrderSessionKey)
   301  	if !found {
   302  		return nil, nil
   303  	}
   304  
   305  	placeOrderInfo, ok := lastPlacedOrder.(PlaceOrderInfo)
   306  	if !ok {
   307  		return nil, errors.New("placeOrderInfo couldn't be received from session")
   308  	}
   309  
   310  	return &placeOrderInfo, nil
   311  }
   312  
   313  // HasLastPlacedOrder returns if a order has been previously placed
   314  func (os *OrderService) HasLastPlacedOrder(ctx context.Context) bool {
   315  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/HasLastPlacedOrder")
   316  	defer span.End()
   317  
   318  	lastPlaced, err := os.LastPlacedOrder(ctx)
   319  
   320  	return lastPlaced != nil && err == nil
   321  }
   322  
   323  // ClearLastPlacedOrder clears the last placed cart, this can be useful if an cart / order is finished
   324  func (os *OrderService) ClearLastPlacedOrder(ctx context.Context) {
   325  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/ClearLastPlacedOrder")
   326  	defer span.End()
   327  
   328  	session := web.SessionFromContext(ctx)
   329  	session.Delete(LastPlacedOrderSessionKey)
   330  }
   331  
   332  // LastPlacedOrCurrentCart returns the decorated cart of the last placed order if there is one if not return the current cart
   333  func (os *OrderService) LastPlacedOrCurrentCart(ctx context.Context) (*decorator.DecoratedCart, error) {
   334  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/LastPlacedOrCurrentCart")
   335  	defer span.End()
   336  
   337  	lastPlacedOrder, err := os.LastPlacedOrder(ctx)
   338  	if err != nil {
   339  		os.logger.Warn("couldn't get last placed order:", err)
   340  
   341  		return nil, err
   342  	}
   343  
   344  	if lastPlacedOrder != nil {
   345  		// cart has been placed early use it
   346  		return os.decoratedCartFactory.Create(ctx, lastPlacedOrder.Cart), nil
   347  	}
   348  
   349  	// cart wasn't placed early, fetch it from service
   350  	decoratedCart, err := os.cartReceiverService.ViewDecoratedCart(ctx, web.SessionFromContext(ctx))
   351  	if err != nil {
   352  		os.logger.WithContext(ctx).Error("ViewDecoratedCart Error:", err)
   353  
   354  		return nil, err
   355  	}
   356  
   357  	return decoratedCart, nil
   358  }
   359  
   360  // placeOrderWithPaymentProcessing after generating the decorated cart, the place order flow
   361  // is the same for the interface functions, therefore the common flow is placed in this private helper function
   362  func (os *OrderService) placeOrderWithPaymentProcessing(ctx context.Context, decoratedCart *decorator.DecoratedCart,
   363  	session *web.Session) (*PlaceOrderInfo, error) {
   364  	ctx, span := trace.StartSpan(ctx, "checkout/OrderService/placeOrderWithPaymentProcessing")
   365  	defer span.End()
   366  
   367  	if !decoratedCart.Cart.IsPaymentSelected() {
   368  		// record noPaymentSelectionCount metric
   369  		stats.Record(ctx, noPaymentSelectionCount.M(1))
   370  		os.logger.WithContext(ctx).Error("cart.checkoutcontroller.submitaction: Error Gateway not in carts PaymentSelection")
   371  
   372  		return nil, errors.New("no payment gateway selected")
   373  	}
   374  
   375  	validationResult := os.cartService.ValidateCart(ctx, session, decoratedCart)
   376  	if !validationResult.IsValid() {
   377  		// record cartValidationFailCount metric
   378  		stats.Record(ctx, cartValidationFailCount.M(1))
   379  		os.logger.WithContext(ctx).Warn("Try to place an invalid cart")
   380  
   381  		return nil, errors.New("cart is invalid")
   382  	}
   383  
   384  	gateway, err := os.GetPaymentGateway(ctx, decoratedCart.Cart.PaymentSelection.Gateway())
   385  	if err != nil {
   386  		// record paymentGatewayNotFoundCount metric
   387  		stats.Record(ctx, paymentGatewayNotFoundCount.M(1))
   388  		os.logger.WithContext(ctx).Error(fmt.Sprintf("cart.checkoutcontroller.submitaction: Error %v  Gateway: %v", err, decoratedCart.Cart.PaymentSelection.Gateway()))
   389  
   390  		return nil, errors.New("selected gateway not available")
   391  	}
   392  
   393  	flowStatus, err := gateway.FlowStatus(ctx, &decoratedCart.Cart, PaymentFlowStandardCorrelationID)
   394  	if err != nil {
   395  		// record paymentFlowStatusErrorCount metric
   396  		stats.Record(ctx, paymentFlowStatusErrorCount.M(1))
   397  
   398  		return nil, err
   399  	}
   400  
   401  	if flowStatus.Status == paymentDomain.PaymentFlowStatusFailed || flowStatus.Status == paymentDomain.PaymentFlowStatusCancelled {
   402  		// record paymentFlowStatusFailedCanceledCount metric
   403  		stats.Record(ctx, paymentFlowStatusFailedCanceledCount.M(1))
   404  		os.logger.WithContext(ctx).Info("cart.checkoutcontroller.submitaction: PaymentFlowStatusFailed or PaymentFlowStatusCancelled: Error ", flowStatus.Error)
   405  
   406  		return nil, flowStatus.Error
   407  	}
   408  
   409  	if flowStatus.Status == paymentDomain.PaymentFlowStatusAborted {
   410  		// record paymentFlowStatusAbortedCount metric
   411  		stats.Record(ctx, paymentFlowStatusAbortedCount.M(1))
   412  		os.logger.WithContext(ctx).Info("cart.checkoutcontroller.submitaction: PaymentFlowStatusAborted: Error ", flowStatus.Error)
   413  
   414  		return nil, flowStatus.Error
   415  	}
   416  
   417  	cartPayment, err := gateway.OrderPaymentFromFlow(ctx, &decoratedCart.Cart, PaymentFlowStandardCorrelationID)
   418  	if err != nil {
   419  		// record orderPaymentFromFlowErrorCount metric
   420  		stats.Record(ctx, orderPaymentFromFlowErrorCount.M(1))
   421  
   422  		return nil, err
   423  	}
   424  
   425  	placedOrderInfos, err := os.cartService.PlaceOrderWithCart(ctx, session, &decoratedCart.Cart, cartPayment)
   426  	if err != nil {
   427  		// record placeOrderFailCount metric
   428  		stats.Record(ctx, placeOrderFailCount.M(1))
   429  		os.logger.WithContext(ctx).Error("Error during place Order: " + err.Error())
   430  
   431  		return nil, err
   432  	}
   433  
   434  	os.logger.WithContext(ctx).Info("Placed Order: ", placedOrderInfos)
   435  
   436  	placeOrderInfo := os.preparePlaceOrderInfo(ctx, decoratedCart.Cart, placedOrderInfos, *cartPayment)
   437  	os.storeLastPlacedOrder(ctx, placeOrderInfo)
   438  
   439  	if flowStatus.Status != paymentDomain.PaymentFlowStatusCompleted {
   440  		err = gateway.ConfirmResult(ctx, &decoratedCart.Cart, cartPayment)
   441  		if err != nil {
   442  			os.logger.WithContext(ctx).Error("Error during gateway.ConfirmResult: " + err.Error())
   443  
   444  			return nil, err
   445  		}
   446  	}
   447  
   448  	// record placeOrderSuccessCount metric
   449  	stats.Record(ctx, placeOrderSuccessCount.M(1))
   450  
   451  	return placeOrderInfo, nil
   452  }
   453  
   454  func (os *OrderService) preparePlaceOrderInfo(ctx context.Context, currentCart cart.Cart, placedOrderInfos placeorder.PlacedOrderInfos, cartPayment placeorder.Payment) *PlaceOrderInfo {
   455  	_, span := trace.StartSpan(ctx, "checkout/OrderService/preparePlaceOrderInfo")
   456  	defer span.End()
   457  
   458  	email := currentCart.GetContactMail()
   459  
   460  	placeOrderInfo := &PlaceOrderInfo{
   461  		ContactEmail: email,
   462  		PlacedOrders: placedOrderInfos,
   463  		Cart:         currentCart,
   464  	}
   465  
   466  	for _, transaction := range cartPayment.Transactions {
   467  		placeOrderInfo.PaymentInfos = append(placeOrderInfo.PaymentInfos, PlaceOrderPaymentInfo{
   468  			Gateway:         cartPayment.Gateway,
   469  			Method:          transaction.Method,
   470  			PaymentProvider: transaction.PaymentProvider,
   471  			Title:           transaction.Title,
   472  			Amount:          transaction.AmountPayed,
   473  			CreditCardInfo:  transaction.CreditCardInfo,
   474  		})
   475  	}
   476  
   477  	return placeOrderInfo
   478  }