보안 결제 확인으로 인증

키타무라 에이지
키타무라 에이지

판매자는 특정 신용카드 또는 은행 계좌에 대한 강력한 고객 인증 (SCA) 절차의 일환으로 보안 결제 확인 (SPC)을 사용할 수 있습니다. WebAuthn이 인증을 수행합니다 (종종 생체 인식을 통해). WebAuthn을 사전에 등록해야 합니다. 자세한 내용은 보안 결제 확인 등록을 참고하세요.

일반적인 구현의 작동 방식

SPC는 고객이 판매자 사이트에서 구매할 때 가장 흔히 사용되며, 신용카드 발급기관 또는 은행이 결제자 인증을 요구합니다.

인증 워크플로

인증 흐름을 살펴보겠습니다.

  1. 고객이 판매자에게 결제 사용자 인증 정보 (예: 신용카드 정보)를 제공합니다.
  2. 판매자는 결제 사용자 인증 정보의 해당 발급기관 또는 은행(신뢰 당사자 또는 RP)에 별도의 인증이 필요한지 확인합니다. 이러한 교환은 예를 들어 EMV® 3-D Secure를 통해 발생할 수 있습니다.
    • RP가 판매자가 SPC를 사용하기를 바라지만 사용자가 이전에 등록한 경우 RP는 결제자가 등록한 사용자 인증 정보 ID 목록과 챌린지로 응답합니다.
    • 인증이 필요하지 않으면 판매자는 거래를 계속 완료할 수 있습니다.
  3. 인증이 필요한 경우 판매자가 브라우저에서 SPC를 지원하는지 확인합니다.
    • 브라우저가 SPC를 지원하지 않으면 기존 인증 흐름을 진행합니다.
  4. 판매자가 SPC를 호출합니다. 브라우저에 확인 대화상자가 표시됩니다.
    • RP에서 전달된 사용자 인증 정보 ID가 없는 경우 기존 인증 흐름으로 돌아갑니다. 인증에 성공하면 SPC 등록을 사용하여 향후 인증을 간소화하는 방안을 고려해 보세요.
  5. 사용자는 기기를 잠금 해제하여 결제 금액과 대상을 확인하고 인증합니다.
  6. 판매자가 인증을 통해 사용자 인증 정보를 수신합니다.
  7. RP는 판매자로부터 사용자 인증 정보를 수신하고 진위성을 확인합니다.
  8. RP가 확인 결과를 판매자에게 전송합니다.
  9. 판매자는 사용자에게 결제 성공 또는 실패 여부를 나타내는 메시지를 표시합니다.

특징 감지

브라우저에서 SPC가 지원되는지 감지하려면 canMakePayment()에 모조 호출을 보내면 됩니다.

판매자 웹사이트에서 기능 감지 SPC를 실행하려면 다음 코드를 복사하여 붙여넣으세요.

const isSecurePaymentConfirmationSupported = async () => {
  if (!'PaymentRequest' in window) {
    return [false, 'Payment Request API is not supported'];
  }

  try {
    // The data below is the minimum required to create the request and
    // check if a payment can be made.
    const supportedInstruments = [
      {
        supportedMethods: "secure-payment-confirmation",
        data: {
          // RP's hostname as its ID
          rpId: 'rp.example',
          // A dummy credential ID
          credentialIds: [new Uint8Array(1)],
          // A dummy challenge
          challenge: new Uint8Array(1),
          instrument: {
            // Non-empty display name string
            displayName: ' ',
            // Transparent-black pixel.
            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==',
          },
          // A dummy merchant origin
          payeeOrigin: 'https://non-existent.example',
        }
      }
    ];

    const details = {
      // Dummy shopping details
      total: {label: 'Total', amount: {currency: 'USD', value: '0'}},
    };

    const request = new PaymentRequest(supportedInstruments, details);
    const canMakePayment = await request.canMakePayment();
    return [canMakePayment, canMakePayment ? '' : 'SPC is not available'];
  } catch (error) {
    console.error(error);
    return [false, error.message];
  }
};

isSecurePaymentConfirmationSupported().then(result => {
  const [isSecurePaymentConfirmationSupported, reason] = result;
  if (isSecurePaymentConfirmationSupported) {
    // Display the payment button that invokes SPC.
  } else {
    // Fallback to the legacy authentication method.
  }
});

사용자 인증

사용자를 인증하려면 secure-payment-confirmation 및 WebAuthn 매개변수를 사용하여 PaymentRequest.show() 메서드를 호출합니다.

다음은 결제 수단의 data 속성인 SecurePaymentConfirmationRequest에 제공해야 하는 매개변수입니다.

매개변수 설명
rpId RP 출처의 호스트 이름(RP ID)입니다.
challenge 재전송 공격을 방지하는 무작위 챌린지
credentialIds 사용자 인증 정보 ID의 배열입니다. WebAuthn의 인증에서 allowCredentials 속성은 PublicKeyCredentialDescriptor 객체의 배열을 허용하지만 SPC에서는 사용자 인증 정보 ID 목록만 전달합니다.
payeeName(선택사항) 수취인 이름입니다.
payeeOrigin 수취인 출처입니다. 위에서 언급한 시나리오에서는 판매자의 출처입니다.
instrument 이미지 리소스를 가리키는 displayName 문자열과 icon URL입니다. 요청이 성공하려면 아이콘을 성공적으로 가져와서 표시해야 함을 지정하는 iconMustBeShown의 선택적 불리언 (기본값 true)입니다.
timeout 트랜잭션 서명 제한 시간(밀리초)
extensions WebAuthn 호출에 확장 프로그램을 추가했습니다. '결제' 광고 확장을 직접 지정할 필요는 없습니다.

다음 코드 예를 확인하세요.

// After confirming SPC is available on this browser via a feature detection,
// fetch the request options cross-origin from the RP server.
const options = fetchFromServer('https://rp.example/spc-auth-request');
const { credentialIds, challenge } = options;

const request = new PaymentRequest([{
  // Specify `secure-payment-confirmation` as payment method.
  supportedMethods: "secure-payment-confirmation",
  data: {
    // The RP ID
    rpId: 'rp.example',

    // List of credential IDs obtained from the RP server.
    credentialIds,

    // The challenge is also obtained from the RP server.
    challenge,

    // A display name and an icon that represent the payment instrument.
    instrument: {
      displayName: "Fancy Card ****1234",
      icon: "https://rp.example/card-art.png",
      iconMustBeShown: false
    },

    // The origin of the payee (merchant)
    payeeOrigin: "https://merchant.example",

    // The number of milliseconds to timeout.
    timeout: 360000,  // 6 minutes
  }
}], {
  // Payment details.
  total: {
    label: "Total",
    amount: {
      currency: "USD",
      value: "5.00",
    },
  },
});

try {
  const response = await request.show();

  // response.details is a PublicKeyCredential, with a clientDataJSON that
  // contains the transaction data for verification by the issuing bank.
  // Make sure to serialize the binary part of the credential before
  // transferring to the server.
  const result = fetchFromServer('https://rp.example/spc-auth-response', response.details);
  if (result.success) {
    await response.complete('success');
  } else {
    await response.complete('fail');
  }
} catch (err) {
  // SPC cannot be used; merchant should fallback to traditional flows
  console.error(err);
}

.show() 함수로 PaymentResponse 객체가 반환됩니다. 단, details에는 RP에서 확인할 수 있는 트랜잭션 데이터(payment)가 포함된 clientDataJSON와 함께 공개 키 사용자 인증 정보가 포함되어 있습니다.

결과로 생성된 사용자 인증 정보는 교차 출처에서 RP로 전송되고 확인되어야 합니다.

RP에서 거래를 확인하는 방법

RP 서버에서 거래 데이터 확인은 결제 프로세스에서 가장 중요한 단계입니다.

RP는 거래 데이터를 확인하기 위해 WebAuthn의 인증 어설션 확인 프로세스를 따를 수 있습니다. 또한 payment를 확인해야 합니다.

clientDataJSON의 페이로드 예시:

{
  "type":"payment.get",
  "challenge":"SAxYy64IvwWpoqpr8JV1CVLHDNLKXlxbtPv4Xg3cnoc",
  "origin":"https://spc-merchant.glitch.me",
  "crossOrigin":false,
  "payment":{
    "rp":"spc-rp.glitch.me",
    "topOrigin":"https://spc-merchant.glitch.me",
    "payeeOrigin":"https://spc-merchant.glitch.me",
    "total":{
      "value":"15.00",
      "currency":"USD"
    },
    "instrument":{
      "icon":"https://cdn.glitch.me/94838ffe-241b-4a67-a9e0-290bfe34c351%2Fbank.png?v=1639111444422",
      "displayName":"Fancy Card 825809751248"
    }
  }
}
  • rp는 RP의 출처와 일치합니다.
  • topOrigin는 RP가 예상하는 최상위 출처 (위 예에서 판매자의 출처)와 일치합니다.
  • payeeOrigin는 사용자에게 표시되어야 하는 수취인의 출처와 일치합니다.
  • total는 사용자에게 표시되어야 하는 거래 금액과 일치합니다.
  • instrument는 사용자에게 표시되어야 하는 결제 수단 세부정보와 일치합니다.
const clientData = base64url.decode(response.clientDataJSON);
const clientDataJSON = JSON.parse(clientData);

if (!clientDataJSON.payment) {
  throw 'The credential does not contain payment payload.';
}

const payment = clientDataJSON.payment;
if (payment.rp !== expectedRPID ||
    payment.topOrigin !== expectedOrigin ||
    payment.payeeOrigin !== expectedOrigin ||
    payment.total.value !== '15.00' ||
    payment.total.currency !== 'USD') {
  throw 'Malformed payment information.';
}

모든 인증 기준이 통과되면 RP는 판매자에게 거래가 완료되었음을 알릴 수 있습니다.

다음 단계