メインコンテンツへスキップ
このページで

Nequiとの連携を開始する#

このページは、DEUNAとのNequiの統合を成功させるための包括的なガイドを提供します。

Nequiは、Bancolombiaによって管理されているデジタルウォレットであり、コロンビアで広く利用されています。

仕組み#

DEUNAとNequiの統合により、ユーザーはDEUNAの決済ウィジェット内で完全に処理されるプッシュ通知フローを通じて支払うことができます。外部へのリダイレクトは不要です。

Proceso de pago

Следующий список описывает действительный процесс оплаты с помощью Nequi:

  1. ユーザーは決済ウィジェット内でNequiを選択します。
  2. El usuario ingresa su número de teléfono real de Nequi.
  3. Nequiモバイルアプリにプッシュ通知が送信されます。
  4. ユーザーはアプリ内で直接支払いを承認します。
  5. DEUNAは支払いステータスを受け取ります。
  6. DEUNAはウィジェットで適切なコールバックイベントをトリガーします:
    • onSuccess 承認された支払いの場合
    • onError 拒否または期限切れの場合

onErrorイベントの処理

DEUNAのウィジェットは、以下の処理を開始します。 onError コールバックの実行は、以下のシナリオにおいて:

  1. チェックアウトフォームに提供された電話番号は有効ですが、Nequiアカウントに関連付けられていません。
  2. ユーザーがNequiアプリからのプッシュ通知を拒否する。

いずれの場合も、DEUNAはエラーコードとエラーメッセージを含むレスポンスを返します。このレスポンスは、ビジネスロジックとユーザーエクスペリエンスに基づいて処理できます。

要件#

以下の内容には、Nequiとの成功的な統合に必要な要件がすべて記載されています。

Nequiのサンドボックスおよび本番環境の認証情報は、BancolombiaまたはNequiの担当者から直接取得する必要があります。

環境で使用:

Nequiとの認証プロセスを完了し、Nequiから提供される以下の認証情報を取得してください。

  • Merchant ID

  • 商用利用パスワード

  • 公開鍵

  • 識別コード

  • 有効期限(最大45分)

NequiでのWebhookの設定#

各店舗で、Nequiセッションにウェブフックを設定してください。

Nequiのウェブフックを設定しない場合、Nequiでのステータス更新には2~5分程度の遅延が発生します。

ウェブフックのエンドポイントは、高い可用性とスケーラビリティを備えていることを確認してください。 必要に応じて、サービス利用不可による支払い確認失敗時に、取引の取り消しを設定できます。

ウェブフックペイロード

Nequiは、以下のJSONペイロードを送信します:

JSON
{
  "commerceCode": "29603",
  "value": "1",
  "phoneNumber": "3195414070",
  "messageId": "60396545535",
  "transactionId": "350-12345-34000201-60396545535",
  "region": "C001",
  "receivedAt": "2023-02-27T15:50:13.527Z",
  "paymentStatus": "SUCCESS"
}

ペイロードフィールド

フィールド説明
commerceCodeあなたの内部のNequi商社コード
value支払い金額
phoneNumber支払いの担当者のモバイル電話番号
messageId一意の取引識別子
transactionId支払い識別子
region支払い地域: P001 (パナマ) または C001 (コロンビア)
receivedAt支払い日時をJSON形式で
paymentStatus支払いステータス: SUCCESS, CANCELED、または DENIED

セキュリティ要件の検証

NequiからのすべてのWebhookリクエストには、検証が必要なセキュリティヘッダーが含まれています:

リクエストヘッダー

JSON
{
  "Content-Type": "application/json",
  "Digest": "SHA-256=43GpOk5L54gfpAMBE0xNX1bj2hJA9JJ1RR0dErHfZhI=",
  "Signature": "keyId=\"YourClientId\",algorithm=\"hmac-sha384\",headers=\"content-type digest\",signature=\"...\""
}

検証プロセス

各リクエストについて、以下の2つの項目を確認する必要があります。

1. ボディのダイジェストの検証

次の Digest ヘッダーには、リクエストボディのSHA-256ハッシュが含まれています:

JavaScript
// Convert body to string
const parsedBody = JSON.stringify(request.body);

// Calculate SHA-256 hash
const calculatedDigest = `SHA-256=${createHash('sha256')
  .update(parsedBody)
  .digest('base64')}`;

// Verify it matches the header
if (calculatedDigest !== request.headers.Digest) {
  return 401; // Invalid Digest
}
2. リクエストの署名の検証

次の Signature ヘッダーは、リクエストがNequiから送信されたことを保証します:

  1. 署名ヘッダーを解析します。
JavaScript
const parts = request.headers.Signature.split(',');
const signature = {};

for (const part of parts) {
  const [key, value] = part.split('=');
  signature[key] = value.slice(1, -1); // Remove quotes
}

// Result:
// {
//   keyId: "YourClientId",
//   algorithm: "hmac-sha384",
//   headers: "content-type digest",
//   signature: "..."
// }
  1. 署名テキストを構築します。
JavaScript
const headerNames = signature.headers.split(' '); // ["content-type", "digest"]
const linesForSignature = [];

for (const headerName of headerNames) {
  const value = request.headers[headerName.toLowerCase()];
  linesForSignature.push(`${headerName}: ${value}`);
}

const textForSignature = linesForSignature.join('\n');
// Result:
// "content-type: application/json
// digest: SHA-256=43GpOk5L54gfpAMBE0xNX1bj2hJA9JJ1RR0dErHfZhI="
  1. HMACを計算し、検証します。
JavaScript
const APP_SECRET = 'YourSharedSecret'; // Store securely, never hardcode

// Calculate HMAC-SHA384
const base64hmac = createHmac('sha384', APP_SECRET)
  .update(textForSignature)
  .digest('base64');

// Convert to base64url format
const calculatedSignature = base64hmac
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/, '');

// Verify signature
if (calculatedSignature !== signature.signature) {
  return 401; // Invalid Signature
}

完全な実装例 (Node.js)

JavaScript
const { createHash, createHmac } = require('node:crypto');

// Store securely - use environment variables
const APP_SECRET = process.env.NEQUI_WEBHOOK_SECRET;

async function handleNequiWebhook(request, response) {
  try {
    // 1. Verify Body Digest
    const parsedBody = JSON.stringify(request.body);
    const calculatedDigest = `SHA-256=${createHash('sha256')
      .update(parsedBody)
      .digest('base64')}`;
    
    if (calculatedDigest !== request.headers.digest) {
      return response.status(401).send('Invalid Digest');
    }

    // 2. Parse Signature Header
    const parts = request.headers.signature.split(',');
    const signatureData = {};
    
    for (const part of parts) {
      const [key, value] = part.split('=');
      signatureData[key] = value.slice(1, -1);
    }

    // 3. Build Signing Text
    const headerNames = signatureData.headers.split(' ');
    const linesForSignature = headerNames.map(name => 
      `${name}: ${request.headers[name.toLowerCase()]}`
    );
    const textForSignature = linesForSignature.join('\n');

    // 4. Verify Signature
    const base64hmac = createHmac('sha384', APP_SECRET)
      .update(textForSignature)
      .digest('base64');
    
    const calculatedSignature = base64hmac
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');

    if (calculatedSignature !== signatureData.signature) {
      return response.status(401).send('Invalid Signature');
    }

    // 5. Process the payment notification asynchronously
    processPaymentAsync(request.body);

    // 6. Respond immediately (within 10 seconds)
    return response.status(200).send('OK');

  } catch (error) {
    console.error('Webhook processing error:', error);
    return response.status(500).send('Internal Server Error');
  }
}

async function processPaymentAsync(paymentData) {
  // Process payment in background
  // Update your database, trigger notifications, etc.
  
  const { transactionId, paymentStatus, value, phoneNumber } = paymentData;
  
  if (paymentStatus === 'SUCCESS') {
    // Handle successful payment
  } else if (paymentStatus === 'CANCELED' || paymentStatus === 'REFUSED') {
    // Handle failed payment
  }
}

Nequiとの統合#

技術要件が設定されたら、段階的な統合を開始できます。

1. 次の操作を実行してください。 order_token

購入を行うには、DEUNA の注文を作成する必要があります。

V2の「Purchase」エンドポイントへのリクエストを送信してください。 注文の作成 endpoint.

API は、 order_tokenを返します。これは、全体のワークフローで使用されます。

2. 次の情報を取得してください。 order_token

V2の「Purchase」エンドポイントへのリクエストを送信してください。 Get Order endpoint.

このトークンを、次の手順で使用してください。

3. 決済ウィジェットの表示

注文トークンを受け取った後、以下の処理を実行できます。 DEUNAウィジェット.

JavaScript
await DeunaSDK.initPaymentWidget({
    orderToken: "<DEUNA order token>",
    callbacks: ...,
    paymentMethods: [
        {
            paymentMethod: "voucher",
            processors: ["nequi_push_voucher"],
        },
    ],
});

4. クレジットカードによる支払い

カード決済リクエストを Purchase V2 エンドポイントとプロセスを実行します。

応答

Card payment exampleJavaScript
{
  "order": {
    "cash_change": 0,
    "currency": "USD",
    "discounts": [],
    "display_items_total_amount": "",
    "display_shipping_amount": "",
    "display_sub_total": "",
    "display_tax_amount": "",
    "display_total_amount": "",
    "display_total_discount": "",
    "gift_card": [],
    "items": [
      {
        "brand": "",
        "category": "",
        "color": "",
        "description": "Papa Fritas",
        "details_url": "",
        "discounts": [],
        "id": "001",
        "image_url": "https://images-staging.getduna.com/95463fb5-6279-4ec3-8ff9-fe07aacd2142/cd928351d12c6b96_domicilio_316_744x744.png?d=600x600",
        "isbn": "",
        "manufacturer": "",
        "name": "Papa Fritas",
        "options": "",
        "quantity": 1,
        "size": "",
        "sku": "",
        "tax_amount": {
          "amount": 44,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "taxable": false,
        "total_amount": {
          "amount": 594,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": "",
          "display_original_amount": "",
          "display_total_discount": "",
          "original_amount": 0,
          "total_discount": 0
        },
        "type": "",
        "unit_price": {
          "amount": 550,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "uom": "",
        "upc": "",
        "weight": {
          "unit": "",
          "weight": 0
        }
      },
      {
        "brand": "",
        "category": "",
        "color": "",
        "description": "Hamburguesa ",
        "details_url": "",
        "discounts": [],
        "id": "002",
        "image_url": "https://images-staging.getduna.com/95463fb5-6279-4ec3-8ff9-fe07aacd2142/cd928351d12c6b96_domicilio_51330_744x744_1646338877.png?d=600x600",
        "isbn": "",
        "manufacturer": "",
        "name": "Hamburguesa",
        "options": "",
        "quantity": 2,
        "size": "",
        "sku": "",
        "tax_amount": {
          "amount": 88,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "taxable": false,
        "total_amount": {
          "amount": 3088,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": "",
          "display_original_amount": "",
          "display_total_discount": "",
          "original_amount": 0,
          "total_discount": 0
        },
        "type": "",
        "unit_price": {
          "amount": 1500,
          "currency": "USD",
          "currency_symbol": "$",
          "display_amount": ""
        },
        "uom": "",
        "upc": "",
        "weight": {
          "unit": "",
          "weight": 0
        }
      }
    ],
    "items_total_amount": 3682,
    "metadata": {
      "key1": "NO REQUERIDO",
      "key2": "NO REQUERIDO"
    },
    "order_id": "order116",
    "payment": {
      "data": {
        "amount": {
          "amount": 3770,
          "currency": "USD"
        },
        "authorization_3ds": {
          "html_content": "<div></div>",
          "url_challenge": "",
          "version": "1.1.1"
        },
        "authorization_code": "TEST00",
        "created_at": "2022-07-22 20:22:48.408419489 +0000 UTC",
        "customer": {
          "email": "jhondoe@deuna.com",
          "id": "xxxxxx-0eb3-450f-8b26-4ff23208470f"
        },
        "from_card": {
          "card_brand": "Visa",
          "first_six": "411111",
          "last_four": "1111"
        },
        "id": "order116",
        "installments": {
          "installment_amount": 5999,
          "installment_rate": 0.12,
          "installment_type": "MCI",
          "installments": 3,
          "plan_id": "7471ed27-094d-44c4-a62d-225644b782f7",
          "plan_option_id": "87309ea8-3942-4fdf-95ec-ce29a792aff2"
        },
        "merchant": {
          "id": "9a85e296-cc3d-454b-b591-208d6e538126",
          "store_code": "all"
        },
        "metadata": {
          "authorization_code": "TEST00"
        },
        "method_type": "credit_card",
        "processor": "paymentez",
        "reason": "",
        "status": "processed",
        "updated_at": "2022-07-22 20:22:48.408809765 +0000 UTC"
      }
    },
    "redirect_url": "",
    "scheduled_at": "",
    "shipping": null,
    "shipping_address": {
      "additional_description": "Piso 9",
      "address_type": "home",
      "address1": "Av. de los Incas 15-33, Ambato 180202, Ecuador",
      "address2": "Av. de los Incas 15-33, Ambato 180202, Ecuador",
      "city": "Ambato",
      "country_code": "EC",
      "created_at": "0001-01-01T00:00:00Z",
      "first_name": "Jhon",
      "id": 0,
      "identity_document": "146565656",
      "is_default": true,
      "last_name": "Doe",
      "lat": -1.2480678792202227,
      "lng": -78.62532788804577,
      "phone": " 946565665",
      "state_name": "Tungurahua",
      "updated_at": "0001-01-01T00:00:00Z",
      "user_id": "xxxxx4e2-xxxx-xxxx-xxxx-xxxxx5b7b2e",
      "zipcode": "180202"
    },
    "shipping_amount": 0,
    "shipping_method": null,
    "shipping_methods": [],
    "shipping_options": {},
    "status": "succeeded",
    "store_code": "",
    "sub_total": 3550,
    "tax_amount": 132,
    "timezone": "",
    "total_amount": 3770,
    "total_discount": 0,
    "user_instructions": "Piso 9",
    "webhook_urls": null
  },
  "order_token": "0b98dbe8-d265-49bc-b80d-536cea46509c"
}

Nequiをテストする#

Nequiをテストするには、Nequi用のテストデータを必要とします。必要に応じて、DEUNA Admin Panelまたは リバンドAPI.