אימות באמצעות 'אישור תשלום מאובטח'

אייג'י קיטמורה
אייג'י קיטמורה

מוכרים יכולים להשתמש באישור תשלום מאובטח (SPC) כחלק מתהליך אימות חזק ללקוח (SCA) בכרטיס אשראי או בחשבון בנק נתון. WebAuthn מבצע את האימות (בדרך כלל באמצעות מידע ביומטרי). חובה להירשם מראש ל-WebAuthn. מידע נוסף בנושא זמין במאמר רישום אישור תשלום מאובטח.

איך פועל הטמעה טיפוסית

השימוש הכי נפוץ ב-SPC הוא כשלקוח מבצע רכישה באתר של מוֹכר, ומנפיק כרטיס האשראי או הבנק דורשים אימות המשלם.

תהליך העבודה לאימות.

הנה שלבי תהליך האימות:

  1. הלקוחות מספקים למוכר את פרטי הכניסה לתשלום (למשל, פרטי כרטיס אשראי).
  2. אם המשלם צריך אימות נפרד, המוכר שואל את הבנק או המנפיק של פרטי הכניסה המתאימים (הצד המשלם או ה-RP). ההחלפה עשויה להתרחש, לדוגמה, באמצעות EMV® 3-D Secure.
    • אם הגורם המוגבל (RP) מבקש שהמוכר ישתמש ב-SPC, ואם המשתמש נרשם בעבר, ה-RP ישלח רשימה של פרטי הכניסה שרשמתם על ידי המשלם ואתגר.
    • אם לא נדרש אימות, המוכר יכול להמשיך בביצוע העסקה.
  3. אם צריך לבצע אימות, המוכר קובע אם הדפדפן תומך ב-SPC.
    • אם הדפדפן לא תומך ב-SPC, ממשיכים בתהליך האימות הקיים.
  4. המוכר מפעיל SPC. בדפדפן תוצג תיבת דו-שיח לאישור.
    • אם לא הועברו מזהים של פרטי כניסה מה-RP, חוזרים לתהליך האימות הקיים. אחרי שהאימות יסתיים בהצלחה, כדאי להשתמש ברישום 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.
  }
});

אימות המשתמש

כדי לאמת את המשתמש, צריך להפעיל את השיטה PaymentRequest.show() עם הפרמטרים secure-payment-confirmation ו-WebAuthn:

אלה הפרמטרים שצריך לספק במאפיין data של אמצעי התשלום, SecurePaymentConfirmationRequest.

פרמטר תיאור
rpId שם המארח של מקור הגורם המוגבל (RP) כמזהה RP.
challenge אתגר אקראי שמונע מתקפות חוזרות.
credentialIds מערך של מזהים של פרטי כניסה. באימות של WebAuthn, הנכס allowCredentials מקבל מערך של אובייקטים של PublicKeyCredentialDescriptor, אבל ב-SPC מעבירים רק רשימה של מזהי פרטי כניסה.
payeeName (אופציונלי) שם מקבל התשלום.
payeeOrigin מקור מקבל התשלום. בתרחיש שצוין למעלה, זהו המקור של המוכר.
instrument מחרוזת עבור displayName וכתובת URL של icon שמפנה למשאב תמונה. ערך בוליאני אופציונלי (ברירת המחדל ל-true) עבור iconMustBeShown, שמציין שסמל חייב להגיע בהצלחה ולהציג את הסמל כדי שהבקשה תצליח.
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 מכיל פרטי כניסה של מפתח ציבורי עם clientDataJSON שמכיל את נתוני הטרנזקציה (payment) לאימות על ידי הגורם המוגבל.

צריך להעביר את פרטי הכניסה שמתקבלים ממקורות שונים ל-RP ולאמת אותם.

איך הגורם המוגבל מאמת את העסקה

השלב החשוב ביותר בתהליך התשלום הוא אימות נתוני הטרנזקציות בשרת ה-RP.

כדי לאמת את נתוני העסקאות, ה-RP יכול לפעול לפי תהליך האימות של טענת האימות של WebAuthn. בנוסף, הם צריכים לאמת את payment.

מטען ייעודי (payload) לדוגמה של 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 תואם למקור של הגורם המוגבל.
  • השדה 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) יכול להודיע למוכר שהעסקה בוצעה בהצלחה.

השלבים הבאים