GoogleCloudPlatform/microservices-demo · critical

could not charge the card: %+v

Error message

could not charge the card: %+v

What it means

chargeCard wraps a failed PaymentService.Charge gRPC call with 'could not charge the card'. The payment was not processed, so the order fails; the underlying error (card declined by the fake payment processor, invalid credit card info, or payment service unavailable) is included via %+v. This is the most customer-impacting failure point in checkout.

Source

Thrown at src/checkoutservice/main.go:374

	return out, nil
}

func (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, toCurrency string) (*pb.Money, error) {
	result, err := pb.NewCurrencyServiceClient(cs.currencySvcConn).Convert(context.TODO(), &pb.CurrencyConversionRequest{
		From:   from,
		ToCode: toCurrency})
	if err != nil {
		return nil, fmt.Errorf("failed to convert currency: %+v", err)
	}
	return result, err
}

func (cs *checkoutService) chargeCard(ctx context.Context, amount *pb.Money, paymentInfo *pb.CreditCardInfo) (string, error) {
	paymentResp, err := pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{
		Amount:     amount,
		CreditCard: paymentInfo})
	if err != nil {
		return "", fmt.Errorf("could not charge the card: %+v", err)
	}
	return paymentResp.GetTransactionId(), nil
}

func (cs *checkoutService) sendOrderConfirmation(ctx context.Context, email string, order *pb.OrderResult) error {
	_, err := pb.NewEmailServiceClient(cs.emailSvcConn).SendOrderConfirmation(ctx, &pb.SendOrderConfirmationRequest{
		Email: email,
		Order: order})
	return err
}

func (cs *checkoutService) shipOrder(ctx context.Context, address *pb.Address, items []*pb.CartItem) (string, error) {
	resp, err := pb.NewShippingServiceClient(cs.shippingSvcConn).ShipOrder(ctx, &pb.ShipOrderRequest{
		Address: address,
		Items:   items})
	if err != nil {
		return "", fmt.Errorf("shipment failed: %+v", err)
	}

View on GitHub (pinned to 72ba613a05)

Solutions

  1. Inspect the wrapped error: distinguish InvalidArgument (bad card data) from Unavailable (service down).
  2. Validate credit card number (Luhn check) and required fields (number, CCV, expiry) before calling PlaceOrder.
  3. Check paymentservice pods/logs and restart if unhealthy.
  4. Ensure the Charge amount is a valid, non-nil Money from a successful currency conversion.
  5. Verify TLS credentials and network policy allow checkout->payment gRPC traffic.

Example fix

// before
paymentResp, err := pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{
	Amount:     amount,
	CreditCard: paymentInfo})
if err != nil {
	return "", fmt.Errorf("could not charge the card: %+v", err)
}
// after
if amount == nil || paymentInfo == nil || paymentInfo.GetCreditCardNumber() == "" {
	return "", status.Error(codes.InvalidArgument, "valid payment information is required")
}
paymentResp, err := pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{
	Amount:     amount,
	CreditCard: paymentInfo})
if err != nil {
	return "", fmt.Errorf("could not charge the card: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if paymentInfo == nil || paymentInfo.GetCreditCardNumber() == "" || paymentInfo.GetCreditCardCvv() == 0 {
	return status.Error(codes.InvalidArgument, "complete credit card details required")
}
if !luhnValid(paymentInfo.GetCreditCardNumber()) {
	return status.Error(codes.InvalidArgument, "invalid credit card number")
}

Type guard

func isChargeable(amount *pb.Money, card *pb.CreditCardInfo) bool {
	return amount != nil && amount.GetUnits() > 0 && card != nil && luhnValid(card.GetCreditCardNumber())
}

Try / catch

txID, err := paymentClient.Charge(ctx, req)
if err != nil {
	if status.Code(err) == codes.Unavailable {
		return status.Error(codes.Unavailable, "payment service temporarily unavailable") // do NOT auto-retry charges
	}
	return fmt.Errorf("could not charge the card: %w", err)
}

Prevention

When it happens

Trigger: pb.NewPaymentServiceClient(cs.paymentSvcConn).Charge(ctx, &pb.ChargeRequest{Amount, CreditCard}) errors: payment service unavailable, malformed CreditCardInfo (invalid number/CCV), or processor-side decline surfaced as an RPC error.

Common situations: Client submits an invalid/expired card number; paymentservice pod down; amount Money struct empty due to an earlier conversion failure; TLS/network misconfig between checkout and payment services.

Related errors


AI-assisted analysis of GoogleCloudPlatform/microservices-demo@72ba613a05 (2026-09-02). Data as JSON: /api/errors/a8b871fdab402316. Report an issue: GitHub.