Apple Pay Developer Guidelines

1. Танилцуулга

Энэхүү гарын авлага нь Apple Pay-ийг вэб болон аппликейшн дээр нэгтгэх дэлгэрэнгүй заавар бөгөөд домэйн баталгаажуулалт, төлбөр боловсруулалт, аюулгүй байдлын нийцлийг хамарна.

2. Тохиргоо & Нэгтгэл

Бид Apple Pay-ийг e-commerce вэб болон iOS аппликейшнд дэмждэг. Дараах диаграммууд нь Bonum болон Мерчант талын бүх үйлдлүүдийг харуулна:

1. Хэрэв танд вэб аппликейшн нэгтгэл хэрэгтэй бол:

Bonum Logo
2. Хэрэв танд аппликейшн нэгтгэл хэрэгтэй бол:
Bonum Logo

3. Вэб Апп Нэгтгэл

3.1 Домэйн Баталгаажуулалт

Bonum Logo
1.ЗААВАР

Домэйн баталгаажуулалт нь таны вэбсайт дээр Apple Pay-ийг идэвхжүүлэхэд шаардлагатай алхам юм. Энэ нь таныг тухайн домэйны эзэмшигч бөгөөд Apple Pay-ээр төлбөр хүлээн авах эрхтэй гэдгийг батлана.

2.ШААРДЛАГА

Эхлэхийн өмнө дараах зүйлсийг хангасан эсэхийг шалгана уу:
- Файл байршуулах вэб сервер эсвэл хостинг орчинд хандах боломж.
- Apple Pay нэгтгэлд ашиглагдах хүчинтэй домэйн нэр.

Bonum Logo
3. ДОМЭЙН БАТАЛГААЖУУЛАЛТЫН АЛХАМУУД

Алхам 1: Баталгаажуулалтын файлыг авах
- Bonum энэ файлыг өгнө (Файлын нэр: apple-developer-merchantid-domain-association)

Алхам 2: Файлыг вэб серверт байршуулах
1. Шаардлагатай директорыг үүсгэх:
Вэб серверийнхээ үндсэн директорт .well-known нэртэй директор үүсгэнэ үү (хэрэв байхгүй бол).

Жишээ зам:
https://yourdomain.com/.well-known/

2. Файлыг байршуулах:
Татаж авсан apple-developer-merchantid-domain-association файлыг .well-known директор дотор байршуулна уу.
Файл нь дараах URL-ээр нийтэд нээлттэй байгаа эсэхийг шалгана уу:
https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association

3. Content-Type:
Apple Pay вэб мерчант баталгаажуулалтын файлыг хөтөч эсвэл HTTP хүсэлтээр хандахад text/plain Content-Type-тай үйлчлэх ёстой.

4. Файлын байршлыг шалгах:
Хөтчийг нээж файлын URL руу очно уу. Файлын агуулга энгийн текст хэлбэрээр харагдах ёстой.

Алхам 3: Бидэнд мэдэгдэх
- Файлыг байршуулсны дараа боломжтой холбоо барих сувгаар бидэнд мэдэгдэнэ үү.

Алхам 4: Apple-ийн домэйн баталгаажуулалт
Манай систем Apple-тай холбогдож таны домэйнийг баталгаажуулна. Энэ процесс ихэвчлэн хэдхэн минутын дотор дуусна. Баталгаажуулалт амжилттай болсон тухай мерчантуудад мэдэгдэнэ.

Bonum Logo
4. АЛДААГ ЗАСАХ

Асуудал 1: Файл олдсонгүй
Шалтгаан: apple-developer-merchantid-domain-association файл .well-known директорт зөв байршаагүй байна. Шийдэл: Файлын замыг шалгаж, файл URL-ээр хандах боломжтой эсэхийг шалгана уу.

Асуудал 2: Хандалт хориглогдсон
Шалтгаан: Таны сервер .well-known директор эсвэл файлд хандалтыг хязгаарласан байна.
Шийдэл: Серверийн зөвшөөрлийг шалгаж, .well-known директор болон файл нийтэд нээлттэй эсэхийг шалгана уу.

3.2 Вэб Апп Хэрэгжүүлэлт

Bonum Logo

APPLE PAY   >   PSP   >   Web App

ШААРДЛАГА

Үргэлжлүүлэхийн өмнө дараах зүйлсийг бэлдсэн эсэхийг шалгана уу:

  • Merchant ID (Merchant ID-г Bonum PSP-ээс өгсөн байх ёстой)
  • Merchant Key (Merchant Key-г Bonum PSP-ээс өгсөн байх ёстой)
  • Demo Web App Source apple-pay-example-v1.0.0.zip (Эх файлуудыг Bonum PSP-ээс өгсөн байх ёстой)
Bonum Logo

APPLE PAY   >   PSP   >   Demo Web App Source

A.) DEMO ЭХ ФАЙЛУУД

apple-pay-example-v1.0.0.zip файлыг задлана уу:

  • index.html - Apple Pay товч болон JavaScript нэгтгэлтэй frontend.
  • server.js - Apple Pay мерчант баталгаажуулалт болон төлбөр боловсруулалтыг зохицуулах backend сервер (Node.js Express-тэй).
  • package.json - Backend-д шаардлагатай хамаарлуудын жагсаалт (жнь: express, node-fetch).
  • package-lock.json - Хамаарлын хувилбаруудыг түгжих автоматаар үүсгэгдсэн файл.
Bonum Logo

Web App   >   Frontend Integration

B.) INDEX.HTML

Тэмдэглэл: Apple Pay товчны хэрэгжүүлэлт


Bonum Logo

Web App   >   Frontend Integration

C.) INDEX.HTML

Тэмдэглэл:
1. "REPLACE_YOUR_MERCHANT_IDEN_TIFIER_HERE"-г өөрийн merchant ID-аар солино уу.
2. "total" нь төлөх дүнг илэрхийлнэ. Төлбөрийн дэлгэрэнгүй мэдээллийг задлахын тулд "lineItems"-ийг ашиглана уу.
3. Шинэ Apple Pay session үүсгэнэ.

function startApplePaySession() {
// Merchant-specific information
const merchantIdentifier = 'REPLACE_YOUR_MERCHANT_IDENTIFIER_HERE'; // Replace with your actual merchant ID

// Payment request details
const paymentRequest = {
  countryCode: 'MN', // Country code for Mongolia
  currencyCode: 'MNT', // Mongolian Tugrik
  supportedNetworks: ['visa', 'masterCard', 'amex'], // Supported card networks
  merchantCapabilities: ['supports3DS'], // Security capabilities
  displayName: 'Your Merchant Name', // Name displayed on the payment sheet
  total: {
    label: 'Your Merchant Name', // Total summary label
    amount: '100', // Total amount (in whole numbers for MNT (100.00 is invalid, 100 - is valid))
  },
  lineItems: [ //optional
    { label: 'Product A', amount: '200' },
    { label: 'Discount', amount: '-100' },
  ],
  applicationData: btoa(merchantIdentifier), // Base64 encoded merchant ID
};

// Ensure amount is a whole number for Mongolian Tugrik (MNT)
if (paymentRequest.currencyCode === 'MNT' && !Number.isInteger(Number(paymentRequest.total.amount))) {
  alert('Amounts in Mongolian Tugrik (MNT) must be whole numbers without decimal points.');
  return;
}

// Create a new Apple Pay session
const session = new ApplePaySession(3, paymentRequest);

// Step 3: Validate the merchant during the payment session
session.onvalidatemerchant = async (event) => {
  try {
    const response = await fetch('https://psp.bonum.mn/api/merchant/validate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ validationURL: event.validationURL }),
    });
    const merchantSession = await response.json();
    session.completeMerchantValidation(merchantSession);
  } catch (error) {
    console.error('Merchant validation failed:', error);
    session.abort();
  }
};
Bonum Logo

Web App   >   Frontend Integration

D.) INDEX.HTML

Тэмдэглэл: Төлбөрийн зөвшөөрлийг зохицуулах.

// Step 4: Handle payment authorization
session.onpaymentauthorized = async (event) => {
    const token = event.payment.token;

    try {
      //frontend will send token to its backend. merchant can customize to their needs.
      const response = await fetch('http://localhost:3000/api/payment/authorize', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token, orderId: 'REPLACE_ORDER_ID_HERE' }),
      });
      const result = await response.json();

      // merchant backend response parsing, customize to your needs.
      if (result.success) {
        session.completePayment(ApplePaySession.STATUS_SUCCESS); //mandatory
        ///alert("Payment succeeded: $ {result.description}");
      } else {
        session.completePayment(ApplePaySession.STATUS_FAILURE); //mandatory
        // alert("Payment failed: $ {result.description}"");
      }
    } catch (error) {
      console.error('Error processing payment:', error);
      session.completePayment(ApplePaySession.STATUS_FAILURE);
      alert('Unexpected error occurred while processing payment.');
    }
  };

  // Step 5: Handle payment cancellation
  session.oncancel = () => {
    console.log('Payment was canceled by the user.');
    alert('Payment was canceled.');
  };

  // Step 6: Begin the session
  session.begin();
}
Bonum Logo

Web App   >   The backend Integration

E.) SERVER.JS

Тэмдэглэл: Төлбөрийн зөвшөөрлийг зохицуулж төлбөрийг боловсруулах.
"REPLACE_YOUR_MERCHANT_KEY"-г өөрийн merchant key-ээр солино уу.

const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const cors = require('cors');
const app = express();
const PORT = 3000;

// Allow specific origins (replace "http://example.com" with your frontend's URL)
app.use(cors({
  origin: 'https://psp.bonum.mn', // Allow this specific origin
}));

// Middleware: Parses incoming JSON request bodies
app.use(bodyParser.json());

/**
 * Endpoint to process payment authorization.
 * The client sends a token and an orderId, which are forwarded to the PSP (Payment Service Provider).
 */
app.post('/api/payment/authorize', async (req, res) => {
  const { token, orderId } = req.body;

  // Step 1: Validate incoming request data
  if (!token || !orderId) {
    return res.status(400).json({
      success: false,
      message: 'Missing token or orderId',
    });
  }

  try {
    // Step 2: Forward the payment data to the PSP
    const response = await axios.post(
      'https://psp.bonum.mn/api/payment/process', // PSP endpoint URL
      {
        token,          // Payment token received from Apple Pay
        order_id: orderId, // Merchant invoice/order id
      },
      {
        headers: {
          'Content-Type': 'application/json',
          'x-merchant-key': 'REPLACE_YOUR_MERCHANT_KEY', // Replace with your actual merchant key
        },
      }
    );
        console.log('resp', response.data);
    // Step 3: Respond to the client with the PSP's response
    res.json({
      success: response.data.success,       // Indicates if the payment was successful
      status_code: response.data.status_code, // PSP's status code for the operation
      orderId: response.data.orderId,       // The order ID provided in the response
      description: response.data.desc,      // Additional information about the payment
    });
  } catch (error) {
    console.error('Error processing payment:', error.message);

    // Step 4: Handle errors from the PSP or network issues
    if (error.response) {
      // Errors returned directly by the PSP
      return res.status(error.response.status).json({
        success: error.response.data.success,       // Whether the PSP recognized the payment as successful
        status_code: error.response.data.status_code, // PSP-specific error code
        orderId: error.response.data.orderId,       // Order ID (if available)
        description: error.response.data.desc,      // PSP-provided error description
      });
    }

    // Step 5: Handle unexpected or generic errors (network, server, etc.)
    res.status(500).json({
      success: false,
      status_code: 500,
      orderId,                           // Original order ID for traceability
      description: 'Unexpected error occurred while processing payment.', // Generic error message
    });
  }
});

// Start the Express server
app.listen(PORT, () => {
  console.log("Server is running on http://localhost:" +PORT);
});
Bonum Logo

Web App   >   The backend integration

F.) ТӨЛБӨР ШАЛГАХ

Тэмдэглэл: Төлбөр амжилтгүй болсон тохиолдолд /api/payment-log/read API-г ашиглаж болно.
"REPLACE_YOUR_MERCHANT_KEY"-г өөрийн merchant key-ээр солино уу.
Энэ API дуудлагыг таны backend дээр хийх ёстой.

func checkPaymentByOrderId(orderId: String) {

  let manager = AFHTTPSessionManager()
  manager.requestSerializer = AFJSONRequestSerializer()
  manager.responseSerializer = AFJSONResponseSerializer()
  manager.responseSerializer.acceptableContentTypes = manager.responseSerializer.acceptableContentTypes?.union(["application/json"])
  //TODO: set Merchant Key here. Bonum will provide Merchant Key.
  manager.requestSerializer.setValue("SET-YOUR-MERCHANT-KEY-HERE", forHTTPHeaderField: "x-merchant-key")


  //Demo URL. Note: This URL charges the bank transaction. Please use small amount when you test
  var urlString = "https://testpsp.bonum.mn/api/payment-log/read"
  //Production URL. When you push your Build to the production
  //var urlString = "https://psp.bonum.mn/api/payment-log/read"

  urlString = urlString + "?order_id=" + orderId
  // Convert the JSON object to a JSON string
  print("API Url: \(urlString)")

  manager.get(urlString,
              parameters: nil,
              headers: nil, progress: { progress in
      print("Download progress: \(progress.totalUnitCount)")
  }, success: { task, responseObject in
      print("Transactions for Order ID: \(orderId)")
      print("GET response: \(responseObject ?? "No response")")
  }, failure: { task, error in
      print("Error on GET request: \(error.localizedDescription)")
  })
}
Bonum Logo

Web App   >   The backend integration

G.) ТӨЛБӨР ШАЛГАХ ХАРИУ

Тэмдэглэл: "success": false нь гүйлгээ ямар нэг шалтгаанаар амжилтгүй болсныг илэрхийлнэ.

Тэмдэглэл: "success": true нь гүйлгээ амжилттай боловсруулагдсаныг илэрхийлнэ.

[
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": false,
      "createdAt": "2025-01-24T03:22:03.135Z"
    },
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": true,
      "createdAt": "2025-01-24T03:22:03.135Z"
    }
]

4. iOS Апп Нэгтгэл

4.1 Apple Payment Сертификат Үүсгэх

Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

ЗААВАР

Apple Payment Certificate үүсгэх зааврыг дагана уу. Сертификат үүсгэсний дараа бидэнд сертификатын файлыг илгээнэ үү.

1. Apple Developer Account руу очно уу
2. Developer бүртгэлээрээ нэвтэрнэ үү
Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

A.) "IDENTIFIERS" СОНГОХ

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

B.) MERCHANT IDS СОНГОХ

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

C.) + ADD ДАРАХ

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

D.) CONTINUE ДАРАХ

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

E.) ТАЙЛБАР БОЛОН MERCHANT ID-Г ДООРХ БАЙДЛААР БӨГЛӨХ:

Merchant ID: [ӨӨРИЙН MERCHANT ID-Г ОРУУЛАХ]
Тэмдэглэл: Таны Merchant ID-г Bonum PSP-ээс имэйлээр илгээсэн. Дэлгэцийн зураг дээрх merchant.portal.mn-г үл тоомсорлоно уу.
Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

F.) REGISTER ДАРАХ:

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

G.) МЕРЧАНТЫН МЭДЭЭЛЭЛ ОРУУЛАХ:

Merchant ID: merchant.portal.mn


Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

H.) APPLE PAYMENT PROCESS СЕРТИФИКАТ ҮҮСГЭХ:

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

I.) CONTINUE ДАРАХ:

Тэмдэглэл: Асуултад "No" гэж сонгоно уу.

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

J.) БАЙРШУУЛАХ:

Хавсаргасан "SigningCertRequest.csr" файлыг байршуулна уу
Тэмдэглэл: .csr файлыг Bonum PSP-ээс имэйлээр илгээсэн.

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

J.) ТАТАХ

Payment Certificate файлыг татаж аваад бидэнд илгээнэ үү.

Bonum Logo

4.2 Апп дотор Хэрэгжүүлэлт

Bonum Logo

APPLE PAY   >   PSP   >   In App

ШААРДЛАГА


1. Үргэлжлүүлэхийн өмнө дараах зүйлсийг бэлдсэн эсэхийг шалгана уу:
  • Xcode Project
  • Merchant ID (Merchant ID-г Bonum PSP-ээс өгсөн байх ёстой бөгөөд та өгсөн зааврын дагуу Merchant ID-гаа үүсгэсэн байх шаардлагатай.)
  • Таны Apple Payment Certificate (Энэ сертификат Bonum PSP серверт амжилттай суулгагдсан байх ёстой.)
  • Merchant Key (Merchant Key-г Bonum PSP-ээс өгсөн байх ёстой)
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

A.) APPLE PAY ЧАДАМЖ НЭМЭХ
Bonum Logo
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

B.) + CAPABILITY ДАРАХ

"Apple Pay" гэж хайна уу

img2
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

C.) APPLE PAY СОНГОХ

Apple Pay идэвхжсэний дараа доорх байдлаар харагдана.
Таны Merchant ID нь 'merchant.your.merchant.id'-ийн оронд харагдах ёстой

img3
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

D.) MERCHANT ID ШАЛГАХ

Таны Merchant ID нь 'merchant.your.merchant.id'-ийн оронд харагдах ёстой.

img4
Bonum Logo

XCODE   >   Select Target   >   ApplePayViewController

E.) SWIFT ЖИШЭЭ КОД
ApplePayViewController.swift-ийг Bonum PSP-ээс өгсөн байх ёстой.
Тэмдэглэл: Зааврыг уншиж //TODO: тэмдэглэгдсэн кодыг өөрийн кодоор солино уу.
Код дотор солих чухал зүйлс:
1. "merchant.your.merchant.id"-г өөрийн Merchant ID-аар солино уу.
2. "SET-YOUR-MERCHANT-KEY-HERE"-г өөрийн Merchant Key-ээр солино уу.
3. generateRandomOrderString функцийн оронд захиалгын ID (нэхэмжлэх, SKU гэх мэт) ашиглана уу. Энэ нь аливаа төлбөрийн зүйлийг өвөрмөц байдлаар тодорхойлох боломжийг олгоно. Бид үүнийг гүйлгээний баталгаажуулалтад ашиглана.

ApplePayViewController.swift нь PassKit болон "AFNetworking" ашигладаг.
  • PassKit бол Apple-ийн хөгжүүлэгчдэд Apple Pay-г нэгтгэх боломжийг олгодог framework юм
  • AFNetworking бол төлбөрийн payload өгөгдлийг Bonum PSP серверт илгээхэд ашиглагддаг нээлттэй эхийн сүлжээний сан юм.
  • Таны view controller нь PKPaymentAuthorizationControllerDelegate методуудыг хэрэгжүүлэх ёстой.
Bonum Logo

XCODE   >   ApplePayViewController.swift

F.) PKPAYMENTBUTTON ХЭРЭГЖҮҮЛЭЛТ
override func viewDidLoad() {
    super.viewDidLoad()

    //TODO: Implement PKPaymentButton with its style here.
    var button: UIButton?
    //Checks whether payment can be made or not.
    if PKPaymentAuthorizationController.canMakePayments() {
        button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
        button?.addTarget(self, action: #selector(startApplePay), for: .touchUpInside)
    } else if PKPaymentAuthorizationController.canMakePayments(usingNetworks: [.masterCard, .visa, .amex]) {
        button = PKPaymentButton(paymentButtonType: .setUp, paymentButtonStyle: .black)
        button?.addTarget(self, action: #selector(setupPressed(_:)), for: .touchUpInside)
    }

    if let button = button {
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

G.) АПП ДОТОР ТӨЛБӨР ЭХЛҮҮЛЭХ

Тэмдэглэл: PKPaymentSummaryItem дээр дүн болон шошгыг тохируулна уу. Олон бараа зарах нь нэг бараа зарахаас ялгаатай. Код дахь жишээнүүдийг үзнэ үү.


@objc func startApplePay() {
    guard PKPaymentAuthorizationController.canMakePayments() else {
        print("Apple Pay is not available on this device.")
        return
    }

    guard PKPaymentAuthorizationController.canMakePayments(usingNetworks: [.masterCard, .visa, .amex]) else {
        print("Apple Pay is not set up with a supported card.")
        return
    }

    let paymentRequest = PKPaymentRequest()
    paymentRequest.merchantIdentifier = "merchant.your.merchant.id" // TODO: Set Your Merchant ID here
    paymentRequest.supportedNetworks = [.masterCard, .visa, .amex]
    paymentRequest.merchantCapabilities = [.threeDSecure]
    paymentRequest.countryCode = "MN" //Country Code of Mongolia
    paymentRequest.currencyCode = "MNT" //Currency code of tugrik

    //TODO: One-time purchase for multiple items. Replace the label and amount here.
    let item1 = PKPaymentSummaryItem(label: "Apple", amount: NSDecimalNumber(string: "5.1"))
    let item2 = PKPaymentSummaryItem(label: "Banana", amount: NSDecimalNumber(string: "4.12"))
    //TODO: This item represents the total of all items. Its amount should be the sum of all individual items. For example: 5.1 + 4.12 = 9.22.
    let total = PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(string: "9.22"))
    paymentRequest.paymentSummaryItems = [item1, item2, total]

    // TODO: One-time purchase for a single item. Replace the label and price here.
    // let total = PKPaymentSummaryItem(label: "Kiwi", amount: NSDecimalNumber(string: "10"))
    // paymentRequest.paymentSummaryItems = [total]

    let paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
    paymentController.delegate = self

    paymentController.present { success in
        if success {
            print("Payment sheet presented successfully.")
        } else {
            print("Failed to present payment sheet.")
        }
    }
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

H.) ТӨЛБӨРИЙН ЗӨВШӨӨРЛИЙГ ЗОХИЦУУЛАХ

Тэмдэглэл:
processPaymentData функц нь гүйлгээг боловсруулна.
convertPKPaymentToJSON функц нь төлбөр боловсруулахад зориулж order_id-тай Payload JSON обьект үүсгэнэ.


func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler: @escaping (PKPaymentAuthorizationResult) -> Void) {
    print("Payment authorized: \(payment.token)")
    print("Payment Network: \(String(describing: payment.token.paymentMethod.network))")
    print("Payment Transaction Identifier: \(payment.token.transactionIdentifier)")
    processPaymentData(payment) { result in
        handler(result)
    }
}

func convertPKPaymentToJSON(payment: PKPayment, orderId: String) -> Any? {
    // Extract the payment token details
    let paymentToken = payment.token
    let paymentData = convertToJson(data: paymentToken.paymentData)
    let transactionIdentifier = paymentToken.transactionIdentifier
    let paymentMethod = paymentToken.paymentMethod
    // Extract billing and shipping contacts
    let billingContact = payment.billingContact
    let shippingContact = payment.shippingContact
    // Construct the JSON object
    var jsonObject: [String: Any] = [:]

    jsonObject["token"] = [
        "paymentData": paymentData,
        "transactionIdentifier": transactionIdentifier,
        "paymentMethod": [
            "displayName": paymentMethod.displayName ?? "",
            "network": paymentMethod.network?.rawValue ?? "",
            "type": paymentMethod.type == .debit ? "debit" :
                paymentMethod.type == .credit ? "credit" : "unknown"
        ],
    ]
    //If unnecessary, don't use
    if let billing = billingContact {
        jsonObject["billingContact"] = [
            "givenName": billing.name?.givenName ?? "",
            "familyName": billing.name?.familyName ?? "",
            "emailAddress": billing.emailAddress ?? "",
            "phoneNumber": billing.phoneNumber?.stringValue ?? ""
        ]
    }
    //If unnecessary, don't use
    if let shipping = shippingContact {
        jsonObject["shippingContact"] = [
            "givenName": shipping.name?.givenName ?? "",
            "familyName": shipping.name?.familyName ?? "",
            "emailAddress": shipping.emailAddress ?? "",
            "phoneNumber": shipping.phoneNumber?.stringValue ?? ""
        ]
    }
    //Your ORDER ID should be set here
    jsonObject["order_id"] = orderId
    return jsonObject
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

I.) ТӨЛБӨР БОЛОВСРУУЛАХ

Тэмдэглэл:
processPaymentData функц нь гүйлгээг боловсруулна.
Зөвлөмж: Сервер-Сервер /api/payment/process API дуудлага. Энэ API дуудлагыг таны backend дээр хийх ёстой.


func processPaymentData(_ payment: PKPayment, completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
    let manager = AFHTTPSessionManager()
    manager.requestSerializer = AFJSONRequestSerializer()
    manager.responseSerializer = AFJSONResponseSerializer()
    manager.responseSerializer.acceptableContentTypes = manager.responseSerializer.acceptableContentTypes?.union(["application/json"])
    //TODO: set Merchant Key here. Bonum will provide Merchant Key.
    manager.requestSerializer.setValue("SET-YOUR-MERCHANT-KEY-HERE", forHTTPHeaderField: "x-merchant-key")

    //TODO: Set Payment process API url here
    //Demo URL. Note: This URL charges the bank transaction. Please use small amount when you test
    let urlString = "https://testpsp.bonum.mn/api/payment/process"
    //Production URL. When you push your Build to the production
    //let urlString = "https://psp.bonum.mn/api/payment/process"

    //TODO: set your order/invoice/sku/ id here.
    let orderId = generateRandomOrderString(length: 20)
    orderIdLbl.text = orderId
    let jsonPayload = convertPKPaymentToJSON(payment: payment, orderId: orderId)

    // Convert the JSON object to a JSON string
    if let jsonData = try? JSONSerialization.data(withJSONObject: jsonPayload ?? "", options: .prettyPrinted) {
        let jsonString = String(data: jsonData, encoding: .utf8)
        print("Payload data: \(jsonString ?? "No response")")
    }

    manager.post(urlString, parameters: jsonPayload, headers: nil, progress: { progress in
        print("Download progress: \(progress.totalUnitCount)")
    }, success: { task, responseObject in
        print("POST response: \(responseObject ?? "No response")")
        // Safely cast responseObject to [String: Any]
        if let responseDict = responseObject as? [String: Any] {
            // Access the boolean field
            if let success = responseDict["success"] as? Bool {
                print("Success: \(success)")  // Output: Success: true/false
                if success {
                    let successResult = PKPaymentAuthorizationResult(status: .success, errors: nil)
                    completion(successResult)
                } else {
                    let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
                    completion(failureResult)
                }
            } else {
                print("Key 'success' is missing or not a boolean.")
                let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
                completion(failureResult)
            }
        } else {
            print("Failed to parse response as dictionary.")
            let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
            completion(failureResult)
        }

    }, failure: { task, error in
        print("Error on GET request: \(error.localizedDescription)")
        let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
        completion(failureResult)
    })
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

J.) ТӨЛБӨР БОЛОВСРУУЛАХАД ИЛГЭЭХ PAYLOAD ӨГӨГДӨЛ
{
   "token":{
      "paymentMethod":{
         "type":"debit",
         "network":"MasterCard",
         "displayName":"MasterCard 1588"
      },
      "transactionIdentifier":"4be88bbec5d887d696b536b87d9a28a25d5ca5e6cbbac6e06bb3c14b0967f16f",
      "paymentData":{
         "data":"lAGF6hAH+z\/vZz1UG",
         "signature":"BkAiA+zmtT6wYi6JxyrpjEqYSYRw82kMhjxXSZIvyd1G9zNgAAAAAAAA==",
         "header":{
            "publicKeyHash":"EalQwLcwQ0Mqh9R1qWtAwsO4Eht+6T2w9pMNypx2sP8=",
            "ephemeralPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzy8Acl6MTWG7luHdWeA\/sI+7mXMczg698cdOB9lYJBVVg2WYNgP4Ila4+vLfqvYQmBurR7wK1UQ2W8Ma2kNUAA==",
            "transactionId":"4be88bbec5d887d696b536b87d9a28a25d5ca5e6cbbac6e06bb3c14b0967f16f"
         },
         "version":"EC_v1"
      }
   },
   "order_id":"1nnuqsc4j6bzpl5hm26z"
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

K.) ТӨЛБӨР ШАЛГАХ

Тэмдэглэл: Төлбөр амжилтгүй болсон тохиолдолд /api/payment-log/read API-г ашиглаж болно.

Зөвлөмж: Сервер-Сервер /api/payment-log/read API дуудлага.
Энэ API дуудлагыг таны backend дээр хийх ёстой.

func checkPaymentByOrderId(orderId: String) {

    let manager = AFHTTPSessionManager()
    manager.requestSerializer = AFJSONRequestSerializer()
    manager.responseSerializer = AFJSONResponseSerializer()
    manager.responseSerializer.acceptableContentTypes = manager.responseSerializer.acceptableContentTypes?.union(["application/json"])
    //TODO: set Merchant Key here. Bonum will provide Merchant Key.
    manager.requestSerializer.setValue("SET-YOUR-MERCHANT-KEY-HERE", forHTTPHeaderField: "x-merchant-key")


    //Demo URL. Note: This URL charges the bank transaction. Please use small amount when you test
    var urlString = "https://testpsp.bonum.mn/api/payment-log/read"
    //Production URL. When you push your Build to the production
    //var urlString = "https://psp.bonum.mn/api/payment-log/read"

    urlString = urlString + "?order_id=" + orderId
    // Convert the JSON object to a JSON string
    print("API Url: \(urlString)")

    manager.get(urlString,
                parameters: nil,
                headers: nil, progress: { progress in
        print("Download progress: \(progress.totalUnitCount)")
    }, success: { task, responseObject in
        print("Transactions for Order ID: \(orderId)")
        print("GET response: \(responseObject ?? "No response")")
    }, failure: { task, error in
        print("Error on GET request: \(error.localizedDescription)")
    })
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

L.) ТӨЛБӨР ШАЛГАХ ХАРИУ

Тэмдэглэл: "success": false нь гүйлгээ ямар нэг шалтгаанаар амжилтгүй болсныг илэрхийлнэ.

Тэмдэглэл: "success": true нь гүйлгээ амжилттай боловсруулагдсаныг илэрхийлнэ.

[
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": false,
      "createdAt": "2025-01-24T03:22:03.135Z"
    },
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": true,
      "createdAt": "2025-01-24T03:22:03.135Z"
    }
]

5. ДЭМЖЛЭГ & ХОЛБОО БАРИХ

Лавлагаа:
Apple Pay on Web Demo by Apple - https://applepaydemo.apple.com
Apple Pay Human Interface Guidelines - https://developer.apple.com/design/human-interface-guidelines/apple-pay
Apple Pay Marketing Resources - https://developer.apple.com/apple-pay/marketing
Apple Pay Marketing Guidelines - https://developer.apple.com/apple-pay/marketing


Нэмэлт тусламж хэрэгтэй бол манай багтай холбогдоно уу:
- И-мэйл: info@bonum.mn
- Утас: +976 7200-5000

1. Introduction

This guide provides detailed instructions on integrating Apple Pay for web and in-app applications, including domain verification, payment processing, and security compliance.

2. Setup & Integration

We support Apple Pay for e-commerce web and iOS applications. The following diagrams show all actions on Bonum and Enterprise user side:

1. If you need web application integration:

Bonum Logo
2. If you need in-application integration:
Bonum Logo

3. Web App Integration

3.1 Domain Verification

Bonum Logo
1.INSTRUCTION

Domain verification is a required step for enabling Apple Pay on your website. It confirms that you own the domain and are authorized to accept payments via Apple Pay.
The process involves placing a special verification file, provided by us (Bonum PSP), on your domain's web server and notifying us to verify it with Apple.

2.PREREQUISITES

Before proceeding, ensure you have:
- Access to your web server or hosting environment to upload files.
- A valid domain name that matches the one used for your Apple Pay integration.

Bonum Logo
3. STEPS FOR DOMAIN VERIFICATION

Step 1: Obtain the Verification File
- Bonum will provide this file (File name is apple-developer-merchantid-domain-association)

Step 2: Place the File on Your Web Server
1. Create the required directory:
On your web server, create a directory named .well-known in the root directory of your domain (if it doesn’t already exist).

Example Path:
https://yourdomain.com/.well-known/

2. Upload the file:
Place the downloaded apple-developer-merchantid-domain-association file inside the .well-known directory.
Ensure the file is accessible publicly via the following URL:
https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association

3. Content-Type:
The Apple Pay web merchant validation file must be served with a Content-Type of text/plain when accessed via a browser or HTTP request.

4. Verify file placement:
Open a browser and navigate to the file's URL. You should see the contents of the file displayed as plain text.

Step 3: Notify Us
- After placing the file, let us be informed by any available communication channels.

Step 4: Domain Verification by Apple
Our system will communicate with Apple to verify your domain. This process usually completes within a few minutes. We will inform merchants once the verification is successful.

Bonum Logo
4. TROUBLESHOOTING

Issue 1: File Not Found
Cause: The apple-developer-merchantid-domain-association file is not correctly placed in the .well-known directory. Solution: Verify the file path and ensure the file is accessible via its URL.

Issue 2: Access Denied
Cause: Your server has restricted access to the .well-known directory or file.
Solution: Check your server's permissions and ensure the .well-known directory and file are publicly accessible.

3.2 Web App Implementation

Bonum Logo

APPLE PAY   >   PSP   >   Web App

PREREQUISITES

Before proceeding, ensure that you have completed:

  • Merchant ID (The Merchant ID should have been provided by Bonum PSP)
  • Merchant Key (The Merchant Key should have been provided by Bonum PSP)
  • Demo Web App Source apple-pay-example-v1.0.0.zip (Source files should have been provided by Bonum PSP)
Bonum Logo

APPLE PAY   >   PSP   >   Demo Web App Source

A.) DEMO SOURCE FILES

Extract apple-pay-example-v1.0.0.zip file in a folder:

  • index.html - The frontend with the Apple Pay button and JavaScript integration.
  • server.js - The backend server (Node.js with Express) that handles Apple Pay merchant validation and payment processing.
  • package.json - Lists dependencies needed for your backend (e.g., express, node-fetch).
  • package-lock.json - Automatically generated to lock dependency versions.
Bonum Logo

Web App   >   Frontend Integration

B.) INDEX.HTML

Note: Apple Pay button implementation


Bonum Logo

Web App   >   Frontend Integration

C.) INDEX.HTML

Note:
1.Replace “REPLACE_YOUR_MERCHANT_IDEN_TIFIER_HERE”with your merchant ID.
2. "total” represents the amount to be charged. To break down the payment details, use "lineItems".
3. Create a new Apple Pay session.

function startApplePaySession() {
// Merchant-specific information
const merchantIdentifier = 'REPLACE_YOUR_MERCHANT_IDENTIFIER_HERE'; // Replace with your actual merchant ID

// Payment request details
const paymentRequest = {
  countryCode: 'MN', // Country code for Mongolia
  currencyCode: 'MNT', // Mongolian Tugrik
  supportedNetworks: ['visa', 'masterCard', 'amex'], // Supported card networks
  merchantCapabilities: ['supports3DS'], // Security capabilities
  displayName: 'Your Merchant Name', // Name displayed on the payment sheet
  total: {
    label: 'Your Merchant Name', // Total summary label
    amount: '100', // Total amount (in whole numbers for MNT (100.00 is invalid, 100 - is valid))
  },
  lineItems: [ //optional
    { label: 'Product A', amount: '200' },
    { label: 'Discount', amount: '-100' },
  ],
  applicationData: btoa(merchantIdentifier), // Base64 encoded merchant ID
};

// Ensure amount is a whole number for Mongolian Tugrik (MNT)
if (paymentRequest.currencyCode === 'MNT' && !Number.isInteger(Number(paymentRequest.total.amount))) {
  alert('Amounts in Mongolian Tugrik (MNT) must be whole numbers without decimal points.');
  return;
}

// Create a new Apple Pay session
const session = new ApplePaySession(3, paymentRequest);

// Step 3: Validate the merchant during the payment session
session.onvalidatemerchant = async (event) => {
  try {
    const response = await fetch('https://psp.bonum.mn/api/merchant/validate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ validationURL: event.validationURL }),
    });
    const merchantSession = await response.json();
    session.completeMerchantValidation(merchantSession);
  } catch (error) {
    console.error('Merchant validation failed:', error);
    session.abort();
  }
};
Bonum Logo

Web App   >   Frontend Integration

D.) INDEX.HTML

Note: Handle payment authorization.

// Step 4: Handle payment authorization
session.onpaymentauthorized = async (event) => {
    const token = event.payment.token;

    try {
      //frontend will send token to its backend. merchant can customize to their needs.
      const response = await fetch('http://localhost:3000/api/payment/authorize', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token, orderId: 'REPLACE_ORDER_ID_HERE' }),
      });
      const result = await response.json();

      // merchant backend response parsing, customize to your needs.
      if (result.success) {
        session.completePayment(ApplePaySession.STATUS_SUCCESS); //mandatory
        ///alert("Payment succeeded: $ {result.description}");
      } else {
        session.completePayment(ApplePaySession.STATUS_FAILURE); //mandatory
        // alert("Payment failed: $ {result.description}"");
      }
    } catch (error) {
      console.error('Error processing payment:', error);
      session.completePayment(ApplePaySession.STATUS_FAILURE);
      alert('Unexpected error occurred while processing payment.');
    }
  };

  // Step 5: Handle payment cancellation
  session.oncancel = () => {
    console.log('Payment was canceled by the user.');
    alert('Payment was canceled.');
  };

  // Step 6: Begin the session
  session.begin();
}
Bonum Logo

Web App   >   The backend Integration

E.) SERVER.JS

Note: Handle payment authorization and process the payment.
Replace “REPLACE_YOUR_MERCHANT_KEY"with your merchant key.

const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const cors = require('cors');
const app = express();
const PORT = 3000;

// Allow specific origins (replace "http://example.com" with your frontend's URL)
app.use(cors({
  origin: 'https://psp.bonum.mn', // Allow this specific origin
}));

// Middleware: Parses incoming JSON request bodies
app.use(bodyParser.json());

/**
 * Endpoint to process payment authorization.
 * The client sends a token and an orderId, which are forwarded to the PSP (Payment Service Provider).
 */
app.post('/api/payment/authorize', async (req, res) => {
  const { token, orderId } = req.body;

  // Step 1: Validate incoming request data
  if (!token || !orderId) {
    return res.status(400).json({
      success: false,
      message: 'Missing token or orderId',
    });
  }

  try {
    // Step 2: Forward the payment data to the PSP
    const response = await axios.post(
      'https://psp.bonum.mn/api/payment/process', // PSP endpoint URL
      {
        token,          // Payment token received from Apple Pay
        order_id: orderId, // Merchant invoice/order id
      },
      {
        headers: {
          'Content-Type': 'application/json',
          'x-merchant-key': 'REPLACE_YOUR_MERCHANT_KEY', // Replace with your actual merchant key
        },
      }
    );
        console.log('resp', response.data);
    // Step 3: Respond to the client with the PSP's response
    res.json({
      success: response.data.success,       // Indicates if the payment was successful
      status_code: response.data.status_code, // PSP's status code for the operation
      orderId: response.data.orderId,       // The order ID provided in the response
      description: response.data.desc,      // Additional information about the payment
    });
  } catch (error) {
    console.error('Error processing payment:', error.message);

    // Step 4: Handle errors from the PSP or network issues
    if (error.response) {
      // Errors returned directly by the PSP
      return res.status(error.response.status).json({
        success: error.response.data.success,       // Whether the PSP recognized the payment as successful
        status_code: error.response.data.status_code, // PSP-specific error code
        orderId: error.response.data.orderId,       // Order ID (if available)
        description: error.response.data.desc,      // PSP-provided error description
      });
    }

    // Step 5: Handle unexpected or generic errors (network, server, etc.)
    res.status(500).json({
      success: false,
      status_code: 500,
      orderId,                           // Original order ID for traceability
      description: 'Unexpected error occurred while processing payment.', // Generic error message
    });
  }
});

// Start the Express server
app.listen(PORT, () => {
  console.log("Server is running on http://localhost:" +PORT);
});
Bonum Logo

Web App   >   The backend integration

F.) CHECK PAYMENT

Note: You can use the /api/payment-log/read API for handling any payment failure cases.
Replace “REPLACE_YOUR_MERCHANT_KEY" with your merchant key.
This API call should be executed on your backend.

func checkPaymentByOrderId(orderId: String) {
  
  let manager = AFHTTPSessionManager()
  manager.requestSerializer = AFJSONRequestSerializer()
  manager.responseSerializer = AFJSONResponseSerializer()
  manager.responseSerializer.acceptableContentTypes = manager.responseSerializer.acceptableContentTypes?.union(["application/json"])
  //TODO: set Merchant Key here. Bonum will provide Merchant Key.
  manager.requestSerializer.setValue("SET-YOUR-MERCHANT-KEY-HERE", forHTTPHeaderField: "x-merchant-key")
  
  
  //Demo URL. Note: This URL charges the bank transaction. Please use small amount when you test
  var urlString = "https://testpsp.bonum.mn/api/payment-log/read"
  //Production URL. When you push your Build to the production
  //var urlString = "https://psp.bonum.mn/api/payment-log/read"
  
  urlString = urlString + "?order_id=" + orderId
  // Convert the JSON object to a JSON string
  print("API Url: \(urlString)")
  
  manager.get(urlString,
              parameters: nil,
              headers: nil, progress: { progress in
      print("Download progress: \(progress.totalUnitCount)")
  }, success: { task, responseObject in
      print("Transactions for Order ID: \(orderId)")
      print("GET response: \(responseObject ?? "No response")")
  }, failure: { task, error in
      print("Error on GET request: \(error.localizedDescription)")
  })
}
Bonum Logo

Web App   >   The backend integration

G.) CHECK PAYMENT RESPONSE

Note: "success": false indicates that the transaction failed for some reason.

Note: "success": true indicates that the transaction was processed successfully.

[
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": false,
      "createdAt": "2025-01-24T03:22:03.135Z"
    },
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": true,
      "createdAt": "2025-01-24T03:22:03.135Z"
    }
]

4. IOS App Integration

4.1 Apple Payment Certificate Creation

Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

INSTRUCTION

Follow the instruction to generate Apple Payment Certificate. After certificate creation, please send us certificate file.

1. Go to Apple Developer Account
2. Log in with your developer account
Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

A.) SELECT "IDENTIFIERS"

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

B.) SELECT MERCHANT IDS

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

C.) CLICK + ADD

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

D.) CLICK CONTINUE

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

E.) FILL DESCRIPTION AND MERCHANT ID AS BELOW:

Merchant ID: [ENTER YOUR MERCHANT ID]
Note: Your Merchant ID has been sent to you via email from Bonum PSP. Please disregard merchant.portal.mn on the screen shot.
Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

F.) CLICK REGISTER:

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

G.) ENTER MERCHANT DETAIL:

Merchant ID: merchant.portal.mn


Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

H.) CREATE APPLE PAYMENT PROCESS CERTIFICATE:

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

I.) CLICK CONTINUE:

Note: Select "No" for the question.

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

J.) UPLOAD:

Upload attached "SigningCertRequest.csr"
Note: The .csr file has been sent to you via email from Bonum PSP.

Bonum Logo
Bonum Logo

APPLE PAY   >   PSP   >   Payment Certificate

J.) DOWNLOAD

Download Payment Certificate file and send Payment Certificate file to us.

Bonum Logo

4.2 In-App Implementation

Bonum Logo

APPLE PAY   >   PSP   >   In App

PREREQUISITES


1. Before proceeding, ensure that you have completed:
  • Xcode Project
  • Merchant ID (The Merchant ID should have been provided by Bonum PSP, and you needed to create your Merchant ID according to the provided instructions.)
  • Your Apple Payment Certificate (This certificate should have been successfully installed on the Bonum PSP server.)
  • Merchant Key (The Merchant Key should have been provided by Bonum PSP)
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

A.) ADD APPLE PAY CAPABILITY
Bonum Logo
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

B.) CLICK + CAPABILITY

Search as “Apple Pay”

img2
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

C.) SELECT APPLE PAY

After Apple Pay is enabled, it appears as shown below.
Your Merchant ID should be displayed in place of 'merchant.your.merchant.id'

img3
Bonum Logo

XCODE   >   Select Target   >   Signing & Capabilities

D.) CHECK YOUR MERCHANT ID

Your Merchant ID should be displayed in place of 'merchant.your.merchant.id' .

img4
Bonum Logo

XCODE   >   Select Target   >   ApplePayViewController

E.) EXAMPLE CODE IN SWIFT
ApplePayViewController.swift should have been provided by Bonum PSP.
Note: Please read the instruction and replace //TODO: highlighted code with your own code.
The important items to replace in the code are:
1. Replace “merchant.your.merchant.id” with your Merchant ID.
2. Replace “SET-YOUR-MERCHANT-KEY-HERE” with your Merchant Key.
3. Use the order ID (invoice, SKU, etc.) instead of the generateRandomOrderString function. This will allow any payment item to be uniquely identified. We will use it for transaction verification.

ApplePayViewController.swift uses PassKit and “AFNetworking”.
  • PassKit is a framework provided by Apple that allows developers to integrate Apple Pay
  • AFNetworking is an open-source networking library used for sending payment payload data to the Bonum PSP server.
  • Your view controller should implement the PKPaymentAuthorizationControllerDelegate methods.
Bonum Logo

XCODE   >   ApplePayViewController.swift

F.) PKPAYMENTBUTTON IMPLEMENTATION
override func viewDidLoad() {
    super.viewDidLoad()
    
    //TODO: Implement PKPaymentButton with its style here.
    var button: UIButton?
    //Checks whether payment can be made or not.
    if PKPaymentAuthorizationController.canMakePayments() {
        button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
        button?.addTarget(self, action: #selector(startApplePay), for: .touchUpInside)
    } else if PKPaymentAuthorizationController.canMakePayments(usingNetworks: [.masterCard, .visa, .amex]) {
        button = PKPaymentButton(paymentButtonType: .setUp, paymentButtonStyle: .black)
        button?.addTarget(self, action: #selector(setupPressed(_:)), for: .touchUpInside)
    }
    
    if let button = button {
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

G.) START PAYMENT IN APP

Note: Set the amount and label on the PKPaymentSummaryItem. Selling a multiple items differs from selling single item. See examples in the code.


@objc func startApplePay() {
    guard PKPaymentAuthorizationController.canMakePayments() else {
        print("Apple Pay is not available on this device.")
        return
    }
    
    guard PKPaymentAuthorizationController.canMakePayments(usingNetworks: [.masterCard, .visa, .amex]) else {
        print("Apple Pay is not set up with a supported card.")
        return
    }
    
    let paymentRequest = PKPaymentRequest()
    paymentRequest.merchantIdentifier = "merchant.your.merchant.id" // TODO: Set Your Merchant ID here
    paymentRequest.supportedNetworks = [.masterCard, .visa, .amex]
    paymentRequest.merchantCapabilities = [.threeDSecure]
    paymentRequest.countryCode = "MN" //Country Code of Mongolia
    paymentRequest.currencyCode = "MNT" //Currency code of tugrik
            
    //TODO: One-time purchase for multiple items. Replace the label and amount here.
    let item1 = PKPaymentSummaryItem(label: "Apple", amount: NSDecimalNumber(string: "5.1"))
    let item2 = PKPaymentSummaryItem(label: "Banana", amount: NSDecimalNumber(string: "4.12"))
    //TODO: This item represents the total of all items. Its amount should be the sum of all individual items. For example: 5.1 + 4.12 = 9.22.
    let total = PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(string: "9.22"))
    paymentRequest.paymentSummaryItems = [item1, item2, total]
    
    // TODO: One-time purchase for a single item. Replace the label and price here.
    // let total = PKPaymentSummaryItem(label: "Kiwi", amount: NSDecimalNumber(string: "10"))
    // paymentRequest.paymentSummaryItems = [total]
    
    let paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
    paymentController.delegate = self
    
    paymentController.present { success in
        if success {
            print("Payment sheet presented successfully.")
        } else {
            print("Failed to present payment sheet.")
        }
    }
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

H.) HANDLE PAYMENT AUTHORIZATION

Note:
processPaymentData function will process the transaction.
convertPKPaymentToJSON function will create Payload JSON object with order_id for payment process.


func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler: @escaping (PKPaymentAuthorizationResult) -> Void) {
    print("Payment authorized: \(payment.token)")
    print("Payment Network: \(String(describing: payment.token.paymentMethod.network))")
    print("Payment Transaction Identifier: \(payment.token.transactionIdentifier)")
    processPaymentData(payment) { result in
        handler(result)
    }
}

func convertPKPaymentToJSON(payment: PKPayment, orderId: String) -> Any? {
    // Extract the payment token details
    let paymentToken = payment.token
    let paymentData = convertToJson(data: paymentToken.paymentData)
    let transactionIdentifier = paymentToken.transactionIdentifier
    let paymentMethod = paymentToken.paymentMethod
    // Extract billing and shipping contacts
    let billingContact = payment.billingContact
    let shippingContact = payment.shippingContact
    // Construct the JSON object
    var jsonObject: [String: Any] = [:]
    
    jsonObject["token"] = [
        "paymentData": paymentData,
        "transactionIdentifier": transactionIdentifier,
        "paymentMethod": [
            "displayName": paymentMethod.displayName ?? "",
            "network": paymentMethod.network?.rawValue ?? "",
            "type": paymentMethod.type == .debit ? "debit" :
                paymentMethod.type == .credit ? "credit" : "unknown"
        ],
    ]
    //If unnecessary, don't use
    if let billing = billingContact {
        jsonObject["billingContact"] = [
            "givenName": billing.name?.givenName ?? "",
            "familyName": billing.name?.familyName ?? "",
            "emailAddress": billing.emailAddress ?? "",
            "phoneNumber": billing.phoneNumber?.stringValue ?? ""
        ]
    }
    //If unnecessary, don't use
    if let shipping = shippingContact {
        jsonObject["shippingContact"] = [
            "givenName": shipping.name?.givenName ?? "",
            "familyName": shipping.name?.familyName ?? "",
            "emailAddress": shipping.emailAddress ?? "",
            "phoneNumber": shipping.phoneNumber?.stringValue ?? ""
        ]
    }
    //Your ORDER ID should be set here
    jsonObject["order_id"] = orderId
    return jsonObject
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

I.) PROCESS PAYMENT

Note:
processPaymentData function will process the transaction.
Recommendation: Server-to-Server /api/payment/process API call. This API call should be executed on your backend.


func processPaymentData(_ payment: PKPayment, completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
    let manager = AFHTTPSessionManager()
    manager.requestSerializer = AFJSONRequestSerializer()
    manager.responseSerializer = AFJSONResponseSerializer()
    manager.responseSerializer.acceptableContentTypes = manager.responseSerializer.acceptableContentTypes?.union(["application/json"])
    //TODO: set Merchant Key here. Bonum will provide Merchant Key.
    manager.requestSerializer.setValue("SET-YOUR-MERCHANT-KEY-HERE", forHTTPHeaderField: "x-merchant-key")
    
    //TODO: Set Payment process API url here
    //Demo URL. Note: This URL charges the bank transaction. Please use small amount when you test
    let urlString = "https://testpsp.bonum.mn/api/payment/process"
    //Production URL. When you push your Build to the production
    //let urlString = "https://psp.bonum.mn/api/payment/process"
    
    //TODO: set your order/invoice/sku/ id here.
    let orderId = generateRandomOrderString(length: 20)
    orderIdLbl.text = orderId
    let jsonPayload = convertPKPaymentToJSON(payment: payment, orderId: orderId)
    
    // Convert the JSON object to a JSON string
    if let jsonData = try? JSONSerialization.data(withJSONObject: jsonPayload ?? "", options: .prettyPrinted) {
        let jsonString = String(data: jsonData, encoding: .utf8)
        print("Payload data: \(jsonString ?? "No response")")
    }
    
    manager.post(urlString, parameters: jsonPayload, headers: nil, progress: { progress in
        print("Download progress: \(progress.totalUnitCount)")
    }, success: { task, responseObject in
        print("POST response: \(responseObject ?? "No response")")
        // Safely cast responseObject to [String: Any]
        if let responseDict = responseObject as? [String: Any] {
            // Access the boolean field
            if let success = responseDict["success"] as? Bool {
                print("Success: \(success)")  // Output: Success: true/false
                if success {
                    let successResult = PKPaymentAuthorizationResult(status: .success, errors: nil)
                    completion(successResult)
                } else {
                    let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
                    completion(failureResult)
                }
            } else {
                print("Key 'success' is missing or not a boolean.")
                let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
                completion(failureResult)
            }
        } else {
            print("Failed to parse response as dictionary.")
            let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
            completion(failureResult)
        }
        
    }, failure: { task, error in
        print("Error on GET request: \(error.localizedDescription)")
        let failureResult = PKPaymentAuthorizationResult(status: .failure, errors: nil)
        completion(failureResult)
    })
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

J.) PAYLOAD DATA FOR PAYMENT PROCESS
{
   "token":{
      "paymentMethod":{
         "type":"debit",
         "network":"MasterCard",
         "displayName":"MasterCard 1588"
      },
      "transactionIdentifier":"4be88bbec5d887d696b536b87d9a28a25d5ca5e6cbbac6e06bb3c14b0967f16f",
      "paymentData":{
         "data":"lAGF6hAH+z\/vZz1UG",
         "signature":"BkAiA+zmtT6wYi6JxyrpjEqYSYRw82kMhjxXSZIvyd1G9zNgAAAAAAAA==",
         "header":{
            "publicKeyHash":"EalQwLcwQ0Mqh9R1qWtAwsO4Eht+6T2w9pMNypx2sP8=",
            "ephemeralPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzy8Acl6MTWG7luHdWeA\/sI+7mXMczg698cdOB9lYJBVVg2WYNgP4Ila4+vLfqvYQmBurR7wK1UQ2W8Ma2kNUAA==",
            "transactionId":"4be88bbec5d887d696b536b87d9a28a25d5ca5e6cbbac6e06bb3c14b0967f16f"
         },
         "version":"EC_v1"
      }
   },
   "order_id":"1nnuqsc4j6bzpl5hm26z"
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

K.) CHECK PAYMENT

Note: You can use the /api/payment-log/read API for handling any payment failure cases.

Recommendation: Server-to-Server /api/payment-log/read API call.
This API call should be executed on your backend.

func checkPaymentByOrderId(orderId: String) {
    
    let manager = AFHTTPSessionManager()
    manager.requestSerializer = AFJSONRequestSerializer()
    manager.responseSerializer = AFJSONResponseSerializer()
    manager.responseSerializer.acceptableContentTypes = manager.responseSerializer.acceptableContentTypes?.union(["application/json"])
    //TODO: set Merchant Key here. Bonum will provide Merchant Key.
    manager.requestSerializer.setValue("SET-YOUR-MERCHANT-KEY-HERE", forHTTPHeaderField: "x-merchant-key")
    
    
    //Demo URL. Note: This URL charges the bank transaction. Please use small amount when you test
    var urlString = "https://testpsp.bonum.mn/api/payment-log/read"
    //Production URL. When you push your Build to the production
    //var urlString = "https://psp.bonum.mn/api/payment-log/read"
    
    urlString = urlString + "?order_id=" + orderId
    // Convert the JSON object to a JSON string
    print("API Url: \(urlString)")
    
    manager.get(urlString,
                parameters: nil,
                headers: nil, progress: { progress in
        print("Download progress: \(progress.totalUnitCount)")
    }, success: { task, responseObject in
        print("Transactions for Order ID: \(orderId)")
        print("GET response: \(responseObject ?? "No response")")
    }, failure: { task, error in
        print("Error on GET request: \(error.localizedDescription)")
    })
}
Bonum Logo

XCODE   >   ApplePayViewController.swift

L.) CHECK PAYMENT RESPONSE

Note: "success": false indicates that the transaction failed for some reason.

Note: "success": true indicates that the transaction was processed successfully.

[
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": false,
      "createdAt": "2025-01-24T03:22:03.135Z"
    },
    {
      "merchant_order_id": "9876543",
      "amount": 5000,
      "success": true,
      "createdAt": "2025-01-24T03:22:03.135Z"
    }
]

5. SUPPORT & CONTACT

References:
Apple Pay on Web Demo by Apple - https://applepaydemo.apple.com
Apple Pay Human Interface Guidelines - https://developer.apple.com/design/human-interface-guidelines/apple-pay
Apple Pay Marketing Resources - https://developer.apple.com/apple-pay/marketing
Apple Pay Marketing Guidelines - https://developer.apple.com/apple-pay/marketing


If you need further assistance, please contact our team:
- Email: info@bonum.mn
- Phone: +976 7200-5000