빌링 서비스 및 오픈소스 도구 PG 연계 테스트 설계서 - SeungpilPark/uEngine-bill GitHub Wiki

Abstract Plugin

PG 사의 는 승인, 구매, Direct 신용카드 구매, 캡쳐, 환불 API 를 제공하기 위해 플러그인은 아래 형식의 abstract 코드 룰을 따르기로 한다. 아래의 코드는 killbill 의 플러그인 api 이다.

public interface PaymentPluginApi {

    /**
     * Authorize a specific amount in the Gateway.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentId       killbill payment id
     * @param kbTransactionId   killbill transaction id
     * @param kbPaymentMethodId killbill payment method id
     * @param amount            amount to charge
     * @param currency          currency
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @return information about the authorization in the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public PaymentTransactionInfoPlugin authorizePayment(UUID kbAccountId, UUID kbPaymentId, UUID kbTransactionId, UUID kbPaymentMethodId, BigDecimal amount, Currency currency, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Capture a specific amount in the Gateway.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentId       killbill payment id
     * @param kbTransactionId   killbill transaction id
     * @param kbPaymentMethodId killbill payment method id
     * @param amount            amount to charge
     * @param currency          currency
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @return information about the capture in the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public PaymentTransactionInfoPlugin capturePayment(UUID kbAccountId, UUID kbPaymentId, UUID kbTransactionId, UUID kbPaymentMethodId, BigDecimal amount, Currency currency, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Charge a specific amount in the Gateway.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentId       killbill payment id
     * @param kbTransactionId   killbill transaction id
     * @param kbPaymentMethodId killbill payment method id
     * @param amount            amount to charge
     * @param currency          currency
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @return information about the payment in the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public PaymentTransactionInfoPlugin purchasePayment(UUID kbAccountId, UUID kbPaymentId, UUID kbTransactionId, UUID kbPaymentMethodId, BigDecimal amount, Currency currency, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Void an authorization in the Gateway.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentId       killbill payment id
     * @param kbTransactionId   killbill transaction id
     * @param kbPaymentMethodId killbill payment method id
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @return information about the capture in the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public PaymentTransactionInfoPlugin voidPayment(UUID kbAccountId, UUID kbPaymentId, UUID kbTransactionId, UUID kbPaymentMethodId, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Credit a specific amount in the Gateway.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentId       killbill payment id
     * @param kbTransactionId   killbill transaction id
     * @param kbPaymentMethodId killbill payment method id
     * @param amount            amount to credit
     * @param currency          currency
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @return information about the credit in the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public PaymentTransactionInfoPlugin creditPayment(UUID kbAccountId, UUID kbPaymentId, UUID kbTransactionId, UUID kbPaymentMethodId, BigDecimal amount, Currency currency, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Process a refund against a given payment.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentId       killbill payment id
     * @param kbTransactionId   killbill transaction id
     * @param kbPaymentMethodId killbill payment method id
     * @param amount            refund amount
     * @param currency          currency
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @return information about the refund in the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public PaymentTransactionInfoPlugin refundPayment(UUID kbAccountId, UUID kbPaymentId, UUID kbTransactionId, UUID kbPaymentMethodId, BigDecimal amount, Currency currency, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Retrieve information about a given payment.
     *
     * @param kbAccountId killbill accountId
     * @param kbPaymentId killbill payment id
     * @param properties  custom properties for the gateway
     * @param context     call context
     * @return information about the payment in the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public List<PaymentTransactionInfoPlugin> getPaymentInfo(UUID kbAccountId, UUID kbPaymentId, Iterable<PluginProperty> properties, TenantContext context)
            throws PaymentPluginApiException;

    /**
     * Search payments.
     * <p>
     * The search is plugin specific, there is no constraint on how the searchKey should be interpreted.
     *
     * @param offset     the offset of the first result
     * @param limit      the maximum number of results to retrieve
     * @param properties custom properties for the gateway
     * @param context    call context
     * @return payments matching the search key
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public Pagination<PaymentTransactionInfoPlugin> searchPayments(String searchKey, Long offset, Long limit, Iterable<PluginProperty> properties, TenantContext context)
            throws PaymentPluginApiException;

    /**
     * Add a payment method for a Killbill account in the gateway.
     * <p>
     * Note: the payment method doesn't exist yet in Killbill when receiving the call in
     * the plugin (kbPaymentMethodId is a placeholder).
     *
     * @param kbAccountId        killbill accountId
     * @param paymentMethodProps payment method details
     * @param setDefault         set it as the default payment method in the gateway
     * @param properties         custom properties for the gateway
     * @param context            call context
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public void addPaymentMethod(UUID kbAccountId, UUID kbPaymentMethodId, PaymentMethodPlugin paymentMethodProps, boolean setDefault, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Delete a payment method in the gateway.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentMethodId killbill payment method id
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public void deletePaymentMethod(UUID kbAccountId, UUID kbPaymentMethodId, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Get payment method details for a given payment method.
     *
     * @param kbAccountId       killbill account id
     * @param kbPaymentMethodId killbill payment method id
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @return PaymentMethodPlugin info for the payment method
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public PaymentMethodPlugin getPaymentMethodDetail(UUID kbAccountId, UUID kbPaymentMethodId, Iterable<PluginProperty> properties, TenantContext context)
            throws PaymentPluginApiException;

    /**
     * Set a payment method as default in the gateway.
     *
     * @param kbAccountId       killbill accountId
     * @param kbPaymentMethodId killbill payment method id
     * @param properties        custom properties for the gateway
     * @param context           call context
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public void setDefaultPaymentMethod(UUID kbAccountId, UUID kbPaymentMethodId, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * This is used to see the view of paymentMethods kept by the plugin or the view of
     * existing payment method on the gateway.
     * <p>
     * Sometimes payment methods have to be added directly to the gateway for PCI compliance issues
     * and so Kill Bill needs to refresh its state.
     *
     * @param kbAccountId        killbill accountId
     * @param refreshFromGateway fetch the list of existing  payment methods from gateway -- if supported
     * @param properties         custom properties for the gateway
     * @param context            call context
     * @return all payment methods for that account
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public List<PaymentMethodInfoPlugin> getPaymentMethods(UUID kbAccountId, boolean refreshFromGateway, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Search payment methods
     * <p>
     * The search is plugin specific, there is no constraint on how the searchKey should be interpreted.
     *
     * @param offset     the offset of the first result
     * @param limit      the maximum number of results to retrieve
     * @param properties custom properties for the gateway
     * @param context    call context
     * @return payment methods matching the search key
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public Pagination<PaymentMethodPlugin> searchPaymentMethods(String searchKey, Long offset, Long limit, Iterable<PluginProperty> properties, TenantContext context)
            throws PaymentPluginApiException;

    /**
     * This is used after Killbill decided to refresh its state from the gateway
     *
     * @param kbAccountId    killbill accountId
     * @param paymentMethods the list of payment methods
     * @param properties     custom properties for the gateway
     * @param context        call context
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public void resetPaymentMethods(UUID kbAccountId, List<PaymentMethodInfoPlugin> paymentMethods, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Build metadata for the client to create a redirect form
     *
     * @param kbAccountId  killbill accountId
     * @param customFields form fields
     * @param properties   custom properties for the gateway
     * @param context      call context
     * @return redirect form metadata
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public HostedPaymentPageFormDescriptor buildFormDescriptor(UUID kbAccountId, Iterable<PluginProperty> customFields, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;

    /**
     * Process a notification from the gateway
     * <p>
     * This potentially does more than just deserialize the payload. The plugin may have to acknowledge it
     * with the gateway.
     *
     * @param notification serialized notification object
     * @param properties   custom properties for the gateway
     * @param context      call context
     * @return gateway notification object used to build the response to the gateway
     * @throws PaymentPluginApiException If any unexpected error occurs
     */
    public GatewayNotification processNotification(String notification, Iterable<PluginProperty> properties, CallContext context)
            throws PaymentPluginApiException;
}

Payment Flows 설계

PG 사의 특징에 따라(대부분 국가별 기능 제한에 따라) 결제 플로우는 Hosted Payment Pages (HPP)와 게이트웨이 통합이라는 두 가지 흐름이 있다.

  • HPP 플로우는 사용자를 제 3 자 웹 사이트로 리디렉션하거나 양식 또는 iframe을 호스팅하여 타사 웹 사이트에 정보를 제출하여 지불이 완전히 아웃소싱되는 경우이다.
  • 게이트웨이 통합 플로우는 고객이 웹 사이트를 떠나지 않고 빌링 프레임워크가 게이트웨이 API를 호출하여 직접 지불을 처리하는 경우이다.

아래의 흐름도에서 다음과 같은 액터를 고려해야 한다. :

  • 브라우저 : 자바스크립트 등을 사용해 결제 흐름 시작하기
  • 판매자 사이트 (Merchants Site) : 고객이 주문을 받고 결제 시스템(빌링 프레임워크)을 보호하는 웹 사이트
  • 결제 : 다양한 결제 수단에 대한 API를 제공하는 결제 시스템 (즉, 특정 PG 결제 플러그인)
  • 지불 제공자 : Payment Service Provider 라고도 하는데, 지불 게이트웨이 또는 단순히 게이트웨이라고 불리는 이것은 지불을 처리 할 주체이다.
  • 액세스 제어 서버 (옵션) : 3D 보안 체크 아웃의 경우 사용자가 일부 제 3 자 엔티티로 리디렉션되어 사용자의 결제 흐름을 추적 할 수 있는 사용자 정의 정보를 입력한다.

HPP 플로우 중에 고객은 자신의 지불 방법 정보 (주요 판매자 사이트 또는 제 3 자 사이트)를 입력 한 다음 해당 정보를 포함하는 양식을 제출하여 지불한다. 이 때 사용자에게 지불 양식을 표시하는 데 필요한 정보를 buildFormDescriptor API 등을 호출하게 할 수 있으면 편리할 수 있다.

API 호출의 결과는 HPP 통합의 유형에 따라 다르다.

지불 양식이 사용자의 웹 사이트에서 호스팅되는 경우 (양식 데이터가 제 3 자 웹 사이트로 전송되는 경우), 응답에는 설정할 정확한 필드가 나열된다. (이름은 게이트웨이에 따라 다르다. 예를 들어 금액 대신 합계 필드가 필요함) 및 숨겨진 필드 (예 : merchantId 값)가 필요하다. 예를 들어 PayPal Payments Standard 버튼의 경우 API는 cmd, hosted_button_id 및 submit 필드의 값을 반환한다. 브라우저는 지불 정보를 지불 제공자에게 제출하고, 지불 제공자는 성공 또는 실패시 고객을 방문 페이지로 리디렉션한다.

결제 양식이 타사 웹 사이트에서 완전히 호스팅되는 경우 응답에 사용자를 리디렉션 할 URL이 포함횐다. 이 URL은 주문 정보를 게이트웨이에 제출하거나 쿼리 매개 변수를 통해 특수 URL을 구성하여 지불 플러그인에서 생성 된 일반 또는 고유 항목 일 수 있다. 예를 들어 Dwolla Forms의 경우 API는 https://forms.dwollalabs.com/john과 같은 URL을 반환한다. 브라우저는 결제 공급자 웹 사이트로 리디렉션되어 고객이 결제를 완료 한 다음 성공 또는 실패시 방문 페이지로 다시 리디렉션됩니다.

다음은 몇 가지 일반적인 장면이다.

게이트웨이 통합의 경우, 지불에 대한 진입 점은 createAuthorization (신용 카드 권한 부여), createPurchase (신용 카드 승인 및 캡처, ACH 이전 시작, Bitcoin wallet 등) 또는 createCredit (이전 지불에 대한 참조없이 페이먼트 메소드에 입금하기 위해) API가 있다. 지불 상태는 플러그인을 호출하기 전에 * _INIT 상태가 된다.

일반적으로 트랜잭션은 대부분의 게이트웨이가 동기식 API를 제공하므로 터미널 상태로 종료된다. 그러나 결제 방법에 따라 플러그인에서 상태를 PENDING으로 설정할 수 있어야 한다. 예를 들어, 3D 보안 트랜잭션의 경우 (사용자가 리디렉션 된 후 지급인이 결제를 확인할 때까지), 자동 이체 (예 : ACH, 승인을 받기까지 며칠이 걸림) 또는 Bitcoin 전송 (트랜잭션이 블록 체인에 의해 확인 될 때까지). 또한 일부 게이트웨이는 항상 동기 응답 (예 : Adyen을 사용하여 자금을 캡처 할 때)을 제공하지는 않는다.이 경우 플러그인은 지불을 터미널 상태로 전환하기 위해 비동기 통지에 의존해야 한다.