ABOUT ME

-

Total
-
  • Go: fiber v2 백엔드에서 토스 페이 API 사용하기
    컴퓨터/Go language 2024. 3. 4. 12:20
    728x90
    반응형

    할 것

    Go언어 백엔드 서버에 toss payments API를 연동해보고 싶었다.

    우선 API 키를 얻어준다. @링크

     

    API 키 | 토스페이먼츠 개발자센터

    토스페이먼츠 클라이언트 키 및 시크릿 키를 발급받고 사용하는 방법을 알아봅니다. 클라이언트 키는 브라우저에서 토스페이먼츠 SDK를 연동할 때 사용합니다. 시크릿 키는 토스페이먼츠 API를

    docs.tosspayments.com

     

    테스트 키로 하면 아무리 결제해도 실제 돈은 빠지지 않는다.

     

    이해하기

    우선 토스 페이먼츠는 Basic 인증 방식을 사용하고 (Authorization: Basic blah 헤더)

    시크릿 키가 ID고 비밀번호는 없는 형태고, 모든 응답/요청은 JSON 이다.

     

    @토스 페이먼츠 개발자 샌드박스

     

    토스페이먼츠 개발자센터

    토스페이먼츠 결제 연동 문서, API, 키, 테스트 내역, 웹훅 등록 등 개발에 필요한 정보와 기능을 확인해 보세요. 결제 연동에 필요한 모든 개발자 도구를 제공해 드립니다.

    developers.tosspayments.com

    위 링크에 들어가서 react와 pure js 버전을 참고하면 좋다.

     

    처음에 백엔드에서 뭘 하고, 프론트에서 뭘 해야하는지 살짝 헷갈렸다.

    /confirm, /success, /fail 이 있는데 백엔드에서 다 처리해야 하나?

     

    물론 케바케지만 샌드박스 형식을 이해한대로 하면

    1. frontend 에서 토스를 부르고 (ck 가 있는 customer key를 이용)

    2. frontend에서 결제를 눌러 결제 요청이

    2.1 성공하면) frontend/success 처럼 router를 만들어서 그 페이지에서 backend/confirm 엔드포인트를 POST 하게 한다. (바로 넘어가던가, 결제 요청 완료 했으니 결제 승인하기!  버튼을 만들어서 넘어가기)

    2.2 실패하면) frontend/fail router를 만들어서 알아서 처리 (물론 에러 코드/메시지는 있다)

    @toss payments doc

     

    Go언어

    fiber v2 + golang v1.22.0 기준이다.

     

    Confirm endpoint

    .env 에 TOSS_SECRET_KEY_TEST를 만들어서 API 값을 넣어줬다.

    var (
    	TOSS_SECRET_KEY      = "Basic " + base64.StdEncoding.EncodeToString([]byte(os.Getenv("TOSS_SECRET_KEY_TEST")+":"))
    	TOSS_CONFIRM_API_URL = "https://api.tosspayments.com/v1/payments/confirm"
    	TOSS_PAYMENT_API_URL = "https://api.tosspayments.com/v1/payments/"
    )
    
    func ConfirmToss(c *fiber.Ctx) error {
    	// Extract paymentKey, orderId, and amount from the request body
    	var requestBody struct {
    		PaymentKey string `json:"paymentKey"`
    		OrderId    string `json:"orderId"`
    		Amount     int    `json:"amount"`
    	}
    	if err := c.BodyParser(&requestBody); err != nil {
    		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
    			"error": "Invalid request body",
    		})
    	}
    
    	// POST agent
    	agent := fiber.Post(TOSS_CONFIRM_API_URL)
    	agent.Set("Authorization", TOSS_SECRET_KEY)
    	agent.ContentType("application/json")
    	agent.Body(c.Body()) // json: {orderId: "aaa", amount: ?, paymentKey: "bbb"}
    
    	// 요청 발생함
    	statusCode, body, errs := agent.Bytes()
    	if len(errs) > 0 {
    		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
    			"errs": errs,
    		})
    	}
        
    	return c.Status(statusCode).Send(body)
    }

     

    그리고 샌드박스 사이트에서 내 백엔드 confirm 링크로 대체해서 테스트 해보니 성공을 한다.

     

    optional) 결제에 관한 모든 정보를 확인하고 싶으면 "https://api.tosspayments.com/v1/payments/"

    에 paymentKey나 orderId를 url에 넣어서 GET 하면 된다. 객체는 다음과 같이 생김

    type Payment struct {
    	Version             string               `json:"version"`
    	PaymentKey          string               `json:"paymentKey"`
    	Type                string               `json:"type"`
    	OrderId             string               `json:"orderId"`
    	OrderName           string               `json:"orderName"`
    	MId                 string               `json:"mId"`
    	Currency            string               `json:"currency"`
    	Method              string               `json:"method"`
    	TotalAmount         float64              `json:"totalAmount"`
    	BalanceAmount       float64              `json:"balanceAmount"`
    	Status              string               `json:"status"`
    	RequestedAt         string               `json:"requestedAt"`
    	ApprovedAt          string               `json:"approvedAt"`
    	UseEscrow           bool                 `json:"useEscrow"`
    	LastTransactionKey  *string              `json:"lastTransactionKey,omitempty"`
    	SuppliedAmount      float64              `json:"suppliedAmount"`
    	Vat                 float64              `json:"vat"`
    	CultureExpense      bool                 `json:"cultureExpense"`
    	TaxFreeAmount       float64              `json:"taxFreeAmount"`
    	TaxExemptionAmount  int                  `json:"taxExemptionAmount"`
    	Cancels             []Cancel             `json:"cancels,omitempty"`
    	IsPartialCancelable bool                 `json:"isPartialCancelable"`
    	Card                *CardInfo            `json:"card,omitempty"`
    	VirtualAccount      *VirtualAccountInfo  `json:"virtualAccount,omitempty"`
    	MobilePhone         *MobilePhoneInfo     `json:"mobilePhone,omitempty"`
    	GiftCertificate     *GiftCertificateInfo `json:"giftCertificate,omitempty"`
    	Transfer            *TransferInfo        `json:"transfer,omitempty"`
    	Receipt             *ReceiptInfo         `json:"receipt,omitempty"`
    	Checkout            *CheckoutInfo        `json:"checkout,omitempty"`
    	EasyPay             *EasyPayInfo         `json:"easyPay,omitempty"`
    	Country             string               `json:"country,omitempty"`
    	Failure             *FailureInfo         `json:"failure,omitempty"`
    	CashReceipt         *CashReceiptInfo     `json:"cashReceipt,omitempty"`
    	CashReceipts        []CashReceiptInfo    `json:"cashReceipts,omitempty"`
    	Discount            *DiscountInfo        `json:"discount,omitempty"`
    }
    
    type CardInfo struct {
    	Amount                float64 `json:"amount"`
    	IssuerCode            string  `json:"issuerCode"`
    	AcquirerCode          *string `json:"acquirerCode,omitempty"`
    	Number                string  `json:"number"`
    	InstallmentPlanMonths int     `json:"installmentPlanMonths"`
    	ApproveNo             string  `json:"approveNo"`
    	UseCardPoint          bool    `json:"useCardPoint"`
    	CardType              string  `json:"cardType"`
    	OwnerType             string  `json:"ownerType"`
    	AcquireStatus         string  `json:"acquireStatus"`
    	IsInterestFree        bool    `json:"isInterestFree"`
    	InterestPayer         *string `json:"interestPayer,omitempty"`
    }
    
    type VirtualAccountInfo struct {
    	AccountType          string                    `json:"accountType"`
    	AccountNumber        string                    `json:"accountNumber"`
    	BankCode             string                    `json:"bankCode"`
    	CustomerName         string                    `json:"customerName"`
    	DueDate              string                    `json:"dueDate"`
    	RefundStatus         string                    `json:"refundStatus"`
    	Expired              bool                      `json:"expired"`
    	SettlementStatus     string                    `json:"settlementStatus"`
    	RefundReceiveAccount *RefundReceiveAccountInfo `json:"refundReceiveAccount,omitempty"`
    	Secret               *string                   `json:"secret,omitempty"`
    }
    
    type MobilePhoneInfo struct {
    	CustomerMobilePhone string `json:"customerMobilePhone"`
    	SettlementStatus    string `json:"settlementStatus"`
    	ReceiptUrl          string `json:"receiptUrl"`
    }
    
    type GiftCertificateInfo struct {
    	ApproveNo        string `json:"approveNo"`
    	SettlementStatus string `json:"settlementStatus"`
    }
    
    type TransferInfo struct {
    	BankCode         string `json:"bankCode"`
    	SettlementStatus string `json:"settlementStatus"`
    }
    
    type ReceiptInfo struct {
    	Url string `json:"url"`
    }
    
    type CheckoutInfo struct {
    	Url string `json:"url"`
    }
    
    type EasyPayInfo struct {
    	Provider       string  `json:"provider"`
    	Amount         float64 `json:"amount"`
    	DiscountAmount float64 `json:"discountAmount"`
    }
    
    type FailureInfo struct {
    	Code    string `json:"code"`
    	Message string `json:"message"`
    }
    
    type CashReceiptInfo struct {
    	Type                   string       `json:"type"`
    	ReceiptKey             string       `json:"receiptKey"`
    	IssueNumber            string       `json:"issueNumber"`
    	ReceiptUrl             string       `json:"receiptUrl"`
    	Amount                 float64      `json:"amount"`
    	TaxFreeAmount          float64      `json:"taxFreeAmount"`
    	IssueStatus            string       `json:"issueStatus"`
    	Failure                *FailureInfo `json:"failure,omitempty"`
    	CustomerIdentityNumber string       `json:"customerIdentityNumber"`
    	RequestedAt            string       `json:"requestedAt"`
    }
    
    type DiscountInfo struct {
    	Amount int `json:"amount"`
    }
    
    type RefundReceiveAccountInfo struct {
    	BankCode      string `json:"bankCode"`
    	AccountNumber string `json:"accountNumber"`
    	HolderName    string `json:"holderName"`
    }
    
    type CashReceipt struct {
    	ReceiptKey             string         `json:"receiptKey"`
    	IssueNumber            string         `json:"issueNumber"`
    	IssueStatus            string         `json:"issueStatus"`
    	Amount                 int            `json:"amount"`
    	TaxFreeAmount          int            `json:"taxFreeAmount"`
    	OrderId                string         `json:"orderId"`
    	OrderName              string         `json:"orderName"`
    	Type                   string         `json:"type"`
    	TransactionType        string         `json:"transactionType"`
    	BusinessNumber         string         `json:"businessNumber"`
    	CustomerIdentityNumber string         `json:"customerIdentityNumber"`
    	Failure                *FailureDetail `json:"failure,omitempty"`
    	RequestedAt            string         `json:"requestedAt"`
    	ReceiptUrl             string         `json:"receiptUrl"`
    }
    
    type FailureDetail struct {
    	Code    string `json:"code"`
    	Message string `json:"message"`
    }
    
    type Cancel struct {
    	CancelAmount          int       `json:"cancelAmount"`
    	CancelReason          string    `json:"cancelReason"`
    	TaxFreeAmount         int       `json:"taxFreeAmount"`
    	TaxExemptionAmount    int       `json:"taxExemptionAmount"`
    	RefundableAmount      int       `json:"refundableAmount"`
    	EasyPayDiscountAmount int       `json:"easyPayDiscountAmount"`
    	CanceledAt            time.Time `json:"canceledAt"`
    	TransactionKey        string    `json:"transactionKey"`
    	ReceiptKey            string    `json:"receiptKey,omitempty"`
    }
    
    ...
    
    // handler
    
    func SuccessToss(c *fiber.Ctx) error {
    	paymentKey := c.Query("paymentKey")
    
    	// Fiber 에서 Get 요청 보내기 (fasthttp 기반 클라이언트임)
    	agent := fiber.Get(TOSS_PAYMENT_API_URL + paymentKey)
    	agent.Set("Authorization", TOSS_SECRET_KEY)
    	agent.ContentType("application/json")
        
    	statusCode, body, errs := agent.Bytes()
    	if len(errs) > 0 {
    		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
    			"errs": errs,
    		})
    	}
    
    	var responseMap dto.Payment
    	err := json.Unmarshal(body, &responseMap)
    	if err != nil {
    		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
    			"error": err.Error(),
    		})
    	}
    
    	log.Printf("Code: %+v", statusCode)
    	log.Printf("errs: %+v", errs)
    	log.Printf("Response: %+v", responseMap)
    
    	return c.Render("success", fiber.Map{
    		"IsSuccess": statusCode == 200,
    		"Response":  responseMap,
    	})
    }
    728x90

    댓글