2

我正在尝试制作一个可以使用 Safaricom 的“Lipa Na M-Pesa”(肯尼亚的东西)向 PayBill 号码发送付款的应用程序。该调用是POST对 URL 的请求:

https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest

带标题:

{
        'Host': 'sandbox.safaricom.co.ke',
        'Authorization': 'Bearer ${await mpesaAccessToken}',
        'Content-Type': 'application/json',
      }

与身体:

{
        "BusinessShortCode": "$businessShortCode",
        "Password": "${generateLnmPassword(timeStamp)}",
        "Timestamp": "$timeStamp",
        "TransactionType": "CustomerPayBillOnline",
        "Amount": "10",
        "PartyA": "$userPhoneNumber",
        "PartyB": "$businessShortCode",
        "PhoneNumber": "$userPhoneNumber",
        "CallBackURL": "?????????????????????????????",
        "AccountReference": "account",
        "TransactionDesc": "test",
      }

我收到了一个访问令牌,生成了一个密码并成功拨打了电话,除了那个 CallBackURL 事情...... M-Pesa 文档这样描述他们的回调:

CallBackURL 这是您希望传递事务结果的端点。注册 URL API 回调的规则相同。

来自事务性请求的所有 API 回调都是 POST 请求,不要指望 GET 请求进行回调。此外,数据没有格式化为 application/x-www-form-urlencoded 格式,它是 application/json,所以不要指望您的语言中通常的 POST 字段/变量中的数据,直接从传入的输入中读取结果溪流。

(此处有更多信息,但您可能需要登录:https ://developer.safaricom.co.ke/get-started参见“Lipa na M-Pesa”)

我的应用托管在 Firebase Cloud Firestore 上。有什么方法可以与他们一起创建一个回调 URL,将他们的回调作为 Firestore 集合中的文档接收?...

或者这是不可能的,因为他们需要授权令牌和东西才能这样做......而且我无法影响 M-Pesa 将发送的标题和正文?

(PS 顺便说一句,我在 Flutter/Dart 中编码,所以请不要用 Javascript 或任何东西回答!我会一无所知...:p Flutter/Dart 或纯文本都可以。谢谢!)

4

2 回答 2

1

云函数不是基于 Dart 的。

请参阅以下解决方案;

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const parse = require("./parse");

admin.initializeApp();

exports.lmno_callback_url = functions.https.onRequest(async (req, res) => {
    const callbackData = req.body.Body.stkCallback;
    const parsedData = parse(callbackData);

    let lmnoResponse = admin.firestore().collection('lmno_responses').doc('/' + parsedData.checkoutRequestID + '/');
    let transaction = admin.firestore().collection('transactions').doc('/' + parsedData.checkoutRequestID + '/');
    let wallets = admin.firestore().collection('wallets');

    if ((await lmnoResponse.get()).exists) {
        await lmnoResponse.update(parsedData);
    } else {
        await lmnoResponse.set(parsedData);
    }
    if ((await transaction.get()).exists) {
        await transaction.update({
            'amount': parsedData.amount,
            'confirmed': true
        });
    } else {
        await transaction.set({
            'moneyType': 'money',
            'type': 'deposit',
            'amount': parsedData.amount,
            'confirmed': true
        });
    }
    let walletId = await transaction.get().then(value => value.data().toUserId);

    let wallet = wallets.doc('/' + walletId + '/');

    if ((await wallet.get()).exists) {
        let balance = await wallet.get().then(value => value.data().moneyBalance);
        await wallet.update({
            'moneyBalance': parsedData.amount + balance
        })
    } else {
        await wallet.set({
            'moneyBalance': parsedData.amount
        })
    }

    res.send("Completed");
});

解析函数。

const moment = require("moment");

function parse(responseData) {
    const parsedData = {};
    parsedData.merchantRequestID = responseData.MerchantRequestID;
    parsedData.checkoutRequestID = responseData.CheckoutRequestID;
    parsedData.resultDesc = responseData.ResultDesc;
    parsedData.resultCode = responseData.ResultCode;

    if (parsedData.resultCode === 0) {
        responseData.CallbackMetadata.Item.forEach(element => {
            switch (element.Name) {
                case "Amount":
                    parsedData.amount = element.Value;
                    break;
                case "MpesaReceiptNumber":
                    parsedData.mpesaReceiptNumber = element.Value;
                    break;
                case "TransactionDate":
                    parsedData.transactionDate = moment(
                        element.Value,
                        "YYYYMMDDhhmmss"
                    ).unix();
                    break;
                case "PhoneNumber":
                    parsedData.phoneNumber = element.Value;
                    break;
            }
        });
    }

    return parsedData;
}

module.exports = parse;
于 2021-05-18T05:58:43.723 回答
1

有什么方法可以与他们一起创建一个回调 URL,将他们的回调作为 Firestore 集合中的文档接收?...

在 Firebase 生态系统中最常见的方法是编写一个HTTPS 云函数,该函数将由 Safaricom 服务调用。

在 Cloud Function 中,您将能够根据 POST 请求的内容更新 Firestore 文档。

就像是:

exports.safaricom = functions.https.onRequest((req, res) => {
    // Get the header and body through the req variable
    // See https://firebase.google.com/docs/functions/http-events#read_values_from_the_request

    return admin.firestore().collection('...').doc('...').update({ foo: bar })
        .then(() => {
            res.status(200).send("OK");
        })
        .catch(error => {
            // ...
            // See https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
        })

});

我确实注意到您要求我们不要“用 Javascript 或任何东西回答”而是在 Flutter/Dart 中,但我认为您无法在 Flutter 中实现它:您需要在您完全控制的环境中实现这个 webhook并公开一个 API 端点,例如您自己的服务器或云函数。

Cloud Functions 乍一看似乎很复杂,但实现 HTTPS Cloud Functions 并没有那么复杂。我建议您阅读入门文档并观看 Firebase视频系列中有关“JavaScript Promises”的三个视频,如果您遇到任何问题,请在 SO 上提出新问题。

于 2020-12-14T10:49:39.583 回答