본문 바로가기

NestJs - 결제 프로세스 구현 / 포트원 & 카카오페이 본문

개발/nest.js

NestJs - 결제 프로세스 구현 / 포트원 & 카카오페이

자전하는명왕성 2023. 2. 14. 23:58

미래의 나에게

포트원 SDK

<!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
<!-- iamport.payment.js -->
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>

 

PG사 & 카드사 프로세스에 대한 간단한 이해

웹 결제에 대한 프로세스는 다음과 같이 이루어진다. 

  • 1. 구매자가 구매할 상품에 대한 정보와 금액을 판매자에게 전달
  • 2. 판매자는 전달받은 금액을 PG사에게 결제해줄 것을 요청
  • 3. PG사는 요청받은 정보를 은행사에게 다시 결제 요청
  • 4. 은행사는 요청받은 금액을 구매자의 계좌에서 출금 후 PG사로 전달
  • 5. PG사는 판매자에게 금액을 전달 (일정량의 수수료를 제외)
  • 6. 판매자는 금액 확인 후, 구매자에게 상품 배송

**PG사 : Payment Gateway 의 준말로, 구매자와 판매자 사이의 결제를 대행해주는 역할을 담당한다.

대표적으로 KG이니시스 / NHN / KCP , 모바일 환경으로는 KG모빌리언스, 다날, 카카오페이 등이 있다.

 

결제 솔루션 이해

결제 솔루션은 PG사와의 결제 시스템을 연결해주는 API 서비스다.

위와 같은 서비스는 포트원(아임포트) / 부트페이가 있는데 포스팅에서는 포트원에 대해 다룬다.

결제 솔루션을 사용할 경우, 개발자 입장에서 복잡한 결제 환경을 직접 구현할 필요 없다는 장점이 있다.

** 모두 할 필요 없을 뿐이지, 사실 신경쓸 것이 많다...

 

포트원 사용하기

https://portone.io/korea/ko

 

포트원, 온라인 비즈니스를 위한 통합 결제 솔루션

코드 한 줄로 세상 모든 방식의 결제를 경험해보세요

portone.io

 

포트원 관리자 콘솔에서는 기본적인 설정을 포함하여 결제와 관련된 모든 API 를 확인할 수 있다.

 

먼저, 로그인 후 결제 연동을 설정한다.

테스트 환경에서 카카오 페이로 진행해보기로 했다.

 

결제에 관해서는 관련 테이블을 만들어 다룰 생각이니 먼저 entity를 만든다.

@Entity()
@ObjectType()
export class Payment {
  @PrimaryGeneratedColumn('uuid')
  @Field(() => String)
  id: string;

  @Column()
  @Field(() => String)
  impUid: string;

  @Column()
  @Field(() => Int)
  amount: number;

// 결제 상태는 enum 으로 다루어, 잘못된 값이 들어오지 않도록 한다.
  @Column({ type: 'enum', enum: POINT_TRANSACTION_STATUS_ENUM })
  @Field(() => POINT_TRANSACTION_STATUS_ENUM)
  status: POINT_TRANSACTION_STATUS_ENUM;

  @CreateDateColumn()
  createAt: Date;

  @ManyToOne(() => Product)
  @Field(() => Product)
  products: Product;

  // 회원
  @ManyToOne(() => User, (users) => users.payment)
  @Field(() => User)
  users: User;
}

 

이후 dto를 작성하는 것이 조금 더 좋겠지만, 아직 어떤 데이터를 받아올지 정해두진 않았으니 생략한다.

 

resolver & service

// payments.resolver.ts

@Resolver()
export class PaymentResolver {
  constructor(
    private readonly paymentService: PaymentService, // DI
  ) {}

  @UseGuards(GqlAuthGuard('access')) // 사용자를 확인하기 위한 guard
  @Mutation(() => Payment) // 리턴으로 받을 값
  createPayment(
    @Args('impUid') impUid: string, // 결제 정보
    @Args({ name: 'amount', type: () => Int }) amount: number, // 금액
    @Context() context: IContext, // req 받을 context
  ): Promise<Payment> {
    const user = context.req.user;
    return this.paymentService.createPayment({ impUid, amount, user });
  }
}

// payments.service.ts

@Injectable()
export class PaymentService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>, // user DB

    @InjectRepository(Payment)
    private readonly paymentRepository: Repository<Payment>, // payment DB
  ) {}

  async createPayment({
    impUid,
    amount: _amount, // 동일 변수명이 다른 의미로 등장할 경우, 언더바를 활용하여 구분한다
// 여기서 _amount는 사용 금액, 아래 amount 는 userRepository 콜론인 내 총 사용금액을 뜻한다.
    user: _user,
  }: IPaymentServiceCreate): Promise<Payment> {
  
    // 1. 테이블에 거래 기록 1줄 생성
    const payment = this.paymentRepository.create({
      impUid,
      amount: _amount,
      users: _user,
      status: POINT_TRANSACTION_STATUS_ENUM.PAYMENT, // 테스트 환경이기에 결제 처리로 처리
    });

    await this.paymentRepository.save(payment);
    
    // 2. 유저의 여태 사용한 금액 파악
    const user = this.userRepository.findOne({
      where: { id: _user.id },
    });
    
    // 3. 유저의 사용한 금액 업데이트
    // 이떄 TypeORM update에는 {조건식}, {업뎃할 내용} 이 들어간다.
    await this.userRepository.update(
      { id: _user.id },
      { amount: (await user).amount + _amount },
    );

    // 4. 최종 결과 브라우저에 돌려주기
    return payment;
  }
}

 

이후 payments.module.ts에서 resolver & service를 providers로 추가하고

app.module.ts에 payments.module.ts 를 imports 로 추가해주면 된다.

 

이때 결제는 rest-API로 진행하는데, graphQL 작성한 로직이 graphQL이라고 하더라도 아래와 같이 가능하다.

const data = await axios.post("http://localhost:3000/graphql",
	{query: `mutation {createPayment(
    	impUid: "${rsp.imp_uid}", amount: ${rsp.paid_amount}) {id}}`,},
	{ headers: { authorization: "Bearer 리프래쉬토큰",},});
return data

 

위와 같이 처리하면 graphQL, HTML 내에서 API 호출을 하더라도 잘 출력되는 것을 확인할 수 있다.

 

Comments