Created
December 7, 2018 18:51
-
-
Save johnotu/80af25cadf1ba3dadaaef7ccdc8e3c91 to your computer and use it in GitHub Desktop.
Code Snippets - Custom Payments for Messenger
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Handle messages received by bot server | |
* | |
*/ | |
'use strict'; | |
// Assume you have these functions defined and exported in your project folder | |
const sendTextMessage = require('../actions/sendTextMsg'); | |
const sendButtonMsg = require('../actions/sendButtonMsg'); | |
const sendReceiptMessage = require('../actions/sendReceiptMsg'); | |
const botApi = require('../utils/botApi'); | |
const receivedMessage = (event, storeName, user, pageObj) => { | |
const senderID = event.sender.id; | |
const recipientID = event.recipient.id; | |
const timeOfMessage = event.timestamp; | |
const message = event.message; | |
console.log('Received message for user %d and page %d at %d with message:', senderID, recipientID, timeOfMessage); | |
const messageId = message.mid; | |
const quickReply = message.quick_reply; | |
if (quickReply) { | |
// Quick reply received | |
const payload = quickReply.payload; | |
console.log('Quick reply for message %s with payload %s', messageId, payload); | |
if (payload.split('***')[0] === 'orderQty') { | |
// Prepare order data for sending to db | |
const orderInfo = payload.split('***'); | |
const qty = orderInfo[1]; const productId = orderInfo[2]; const productName = orderInfo[3]; const productPrice = orderInfo[4]; const productImage = orderInfo[5]; const productDesc = orderInfo[6]; | |
const orderedProducts = [{ | |
product_id: productId, | |
product_qty: qty, | |
product_name: productName, | |
product_price: productPrice, | |
product_image: productImage, | |
product_description: productDesc | |
}]; | |
const orderOwner = { | |
'name': user.name, | |
'address': user.address, | |
'phone_number': user.phone_number, | |
'receipt_address': { | |
'street_1': user.address, | |
'city': user.city, | |
'postal_code': user.postal_code, | |
'state': user.state, | |
'country': user.country_code | |
} | |
}; | |
const orderTotal = orderedProducts.reduce((total, product) => { | |
return total + (parseInt(product.product_price) * parseInt(product.product_qty)); | |
}, 0); | |
const orderNumber = botApi.getOrderNumber(); | |
const orderData = { | |
'order_number': orderNumber, | |
'order_items': orderedProducts, | |
'order_total': orderTotal, | |
'order_owner': orderOwner, | |
'order_status': 'Pending' | |
}; | |
botApi.postOrder(storeName, orderData, (err, resp, body) => { | |
if (!err && resp.statusCode === 200) { | |
const submittedOrder = JSON.parse(body).order; | |
const msg = `Thank you ${user.first_name}, your order #${submittedOrder.orderNumber} has been received. Please find your invoice attached below.`; | |
sendTextMessage({ | |
recipientId: senderID, | |
messageText: msg, | |
page_token: pageObj.page_token, | |
callback: () => { | |
// Send user a receipt | |
sendReceiptMessage(senderID, submittedOrder, pageObj.page_token, () => { | |
// Send payment options to user | |
const buttonMsg = { | |
attachment: { | |
type: 'template', | |
payload: { | |
template_type: 'button', | |
text: 'How would you like to pay for your order?', | |
buttons: [{ | |
type: 'postback', | |
payload: `pay_mobilemoney_${submittedOrder.order_number}_${submittedOrder.order_total}_${submittedOrder.order_owner.phone_number}`, | |
title: 'Mobile Money (Ghana)' | |
}, | |
{ | |
type: 'web_url', | |
url: `${process.env.SERVER_URL}payment?store_name=${storeName}&user_id=${senderID}&mm_number=${submittedOrder.order_owner.phone_number}&page_name=${pageObj.page_name}&page_id=${pageObj.page_id}&order_number=${submittedOrder.order_number}&order_total=${submittedOrder.order_total}&order_id=${submittedOrder.order._id}`, | |
title: 'Card', | |
webview_height_ratio: 'compact', | |
messenger_extensions: true | |
}, | |
{ | |
type: 'postback', | |
payload: `pay_cashondelivery_${submittedOrder.order_number}_${submittedOrder.order_total}_${submittedOrder.order_owner.phone_number}`, | |
title: 'Cash on delivery' | |
}] | |
} | |
} | |
}; | |
sendButtonMsg(senderID, buttonMsg, pageObj.page_token); | |
}); | |
} | |
}); | |
} else { | |
// You should handle errors here | |
} | |
}); | |
} | |
} | |
}; | |
module.exports = receivedMessage; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Handle postbacks received by bot server | |
* | |
*/ | |
'use strict'; | |
// Assume you have these functions defined and exported in your project folder | |
const sendTextMessage = require('../actions/sendTextMsg'); | |
const processMobilemoney = require('./process-mobilemoney-gh'); | |
const receivedPostback = (event, pageObj) => { | |
const senderID = event.sender.id; | |
const payload = event.postback.payload; | |
if (payload.split('_')[0] === 'pay') { | |
// Get order details for processing | |
const payData = payload.split('_'); | |
const payMethod = payData[1]; | |
const orderObj = { | |
orderNumber: payData[2], | |
orderTotal: payData[3], | |
orderOwnerPhone: payData[4] | |
}; | |
switch (payMethod) { | |
case 'cashondelivery': | |
sendTextMessage({ | |
recipientId: senderID, | |
messageText: `You have choosen to pay Cash on Delivery of order #${orderObj.orderNumber}. Expect your delivery soon!`, | |
page_token: pageObj.page_token | |
}); | |
break; | |
case 'mobilemoney': | |
// Get number prefix, network and route for mobile money processing | |
const numberPrefix = orderObj.orderOwnerPhone.slice(0, 3); | |
const network = ''; | |
const route = ''; | |
processMobilemoney(senderID, orderObj, numberPrefix, network, route, pageObj); | |
break; | |
// You can add more cases for other payment method implementations here | |
} | |
} | |
}; | |
module.exports = receivedPostback; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html lang="en"> | |
<head> | |
<title>Card payment</title> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" | |
crossorigin="anonymous"> > | |
<meta name="viewport" , content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"> | |
<style> | |
#item-list { | |
margin-top: 3.5em; | |
} | |
</style> | |
</head> | |
<body> | |
<nav class="navbar fixed-top navbar-light" style="background-color: #ecf0f1"> | |
<div> | |
<h5 class="font-weight-bold"> | |
<%= page_name %> | |
</h5> | |
</div> | |
</nav> | |
<div id="item-list"> | |
<div class='container'> | |
<h5>Payment for Order # | |
<%= order_number %> | |
</h5> | |
<div id="paystackEmbedContainer"></div> | |
</div> | |
</div> | |
<script type="text/javascript" src="https://ravesandboxapi.flutterwave.com/flwv3-pug/getpaidx/api/flwpbf-inline.js"></script> | |
<script> | |
// Facebook JavaScript SDK | |
(function (d, s, id) { | |
var js, fjs = d.getElementsByTagName(s)[0]; | |
if (d.getElementById(id)) { return; } | |
js = d.createElement(s); js.id = id; | |
js.src = "//connect.facebook.com/en_US/messenger.Extensions.js"; | |
fjs.parentNode.insertBefore(js, fjs); | |
}(document, 'script', 'Messenger')); | |
window.extAsyncInit = function () { | |
} | |
// Get URL parameters | |
function getParameterByName(name, url) { | |
if (!url) url = window.location.href; | |
name = name.replace(/[\[\]]/g, "\\$&"); | |
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), | |
results = regex.exec(url); | |
if (!results) return null; | |
if (!results[2]) return ''; | |
return decodeURIComponent(results[2].replace(/\+/g, " ")); | |
} | |
let store_name = getParameterByName('store_name'), userPSID = getParameterByName('user_id'), page_id = getParameterByName('page_id'); | |
const API_publicKey = "FLWPUBK-144f6ce78a70af769b682b23c55c5396-X"; | |
// Made this an IIFE so it runs as soon as page loads | |
(function payWithRave() { | |
var x = getpaidSetup({ | |
PBFPubKey: API_publicKey, | |
customer_email: "<%= user_email %>", | |
amount: '<%= order_total %>', | |
custom_logo: 'https://i.postimg.cc/nc49nnqc/jstitbit-logo.png', | |
customer_phone: "233264537375", | |
currency: "GHS", | |
country: 'GH', | |
txref: "rave-test-jstitbit-123456", | |
meta: [{ | |
metaname: "orderNumber", | |
metavalue: "<%=order_number%>" | |
}], | |
onclose: function () { }, | |
callback: function (response) { | |
var txRef = response.tx.txRef; | |
let confirmReq = new Request(`/payment/card/status`, { | |
method: 'POST', | |
headers: new Headers({ | |
'Content-Type': 'application/json', | |
'bot-token': '234dhfjrtuyildkjeuuthnselki' | |
}), | |
body: JSON.stringify({ | |
"response": response, | |
"order_id": '<%= order_id %>', | |
"order_number": '<%= order_number %>', | |
"user_psid": userPSID, | |
"page_id": page_id | |
}) | |
}); | |
fetch(confirmReq) | |
.then( | |
response => { | |
if (response.status !== 200) { | |
console.log('Looks like there was a problem. Status Code: ', response.status); | |
return; | |
} else { | |
response.json().then(data => { | |
// Close payment modal | |
x.close(); | |
// Close Messenger webview and return to conversation thread | |
MessengerExtensions.requestCloseBrowser(function success() { | |
//console.log(success) | |
}, function error(err) { | |
console.log(err) | |
}); | |
}) | |
} | |
} | |
) | |
.catch(err => { | |
console.log('Fetch Error :-S', err); | |
}); | |
} | |
}); | |
}()); | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Handle card payment using Flutterwave API | |
* | |
*/ | |
'use strict'; | |
const express = require('express'); | |
const router = express.Router(); | |
const request = require('request'); | |
const botApi = require('../utils/botApi'); | |
const sendTextMessage = require('../actions/sendTextMsg'); | |
/** | |
* GET route to render payment page in webview | |
*/ | |
router.get('/', (req, res) => { | |
// Render payment webview with order, user and page data | |
return res.render('payment-webview', { | |
order_number: req.query.order_number, | |
order_total: req.query.order_total, | |
user_email: `${req.query.mm_number}@aidahbot.com`, | |
user_psid: req.query.user_id, | |
page_id: req.query.page_id, | |
page_name: req.query.page_name, | |
order_id: req.query.order_id | |
}); | |
}); | |
/** | |
* POST route to verify status of card payment | |
*/ | |
router.post('/card/status', (req, res) => { | |
// Send a 200 status to webview to trigger closing | |
res.send({ status: 200, msg: 'Payment to be confirmed' }); | |
// Verify payment | |
const response = req.body.response; const orderId = req.body.order_id; | |
if ( | |
response.tx.chargeResponseCode === '00' || | |
response.tx.chargeResponseCode === '0' | |
) { | |
request({ | |
url: 'https://ravesandboxapi.flutterwave.com/flwv3-pug/getpaidx/api/v2/verify', | |
header: { 'content-type': 'application/json' }, | |
method: 'POST', | |
json: { | |
'SECKEY': process.env.FLUTTERWAVE_SECKEY, | |
'txref': response.tx.txRef | |
} | |
}, (err, resp, body) => { | |
if (!err && resp.statusCode === 200) { | |
const verifyResp = body; | |
botApi.getOrder(orderId, (err, resp, body) => { | |
if (!err && resp.statusCode === 200) { | |
const order = JSON.parse(body).order; | |
if (verifyResp.status === 'success' && verifyResp.data.chargecode === '00') { | |
const pageObj = botApi.getPage(req.body.page_id); | |
if (verifyResp.data.amount === parseInt(order.order_total) && req.body.order_number === order.order_number) { | |
// Payment was successful | |
sendTextMessage({ | |
messageText: `Thank you ${order.order_owner.name}! We have received your payment of GH¢${order.order_total} for order #${order.order_number}`, | |
recipientId: req.body.user_psid, | |
page_token: pageObj.page_token | |
}); | |
} else { | |
// Payment failed for some reason | |
sendTextMessage({ | |
messageText: `Oops! Something went wrong while we were processing your payment for order #${order.order_number}. Please try again or contact support`, | |
recipientId: req.body.user_psid, | |
page_token: pageObj.page_token | |
}); | |
} | |
} | |
} | |
}); | |
} | |
}); | |
} else { | |
// redirect to a failure page. | |
}; | |
}); | |
module.exports = router; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Process mobile money payments using Mazzuma API | |
* | |
*/ | |
'use strict'; | |
const request = require('request'); | |
const sendTextMessage = require('../actions/sendTextMsg'); | |
const processMobilemoney = (senderID, orderObj, numberPrefix, network, route, pageObj) => { | |
// Check if number prefix matches a Ghanaian telco | |
if (!(['023', '024', '054', '055', '026', '056', '027', '057', '020', '050', '028'].includes(numberPrefix))) { | |
sendTextMessage({ | |
recipientId: senderID, | |
messageText: | |
"Sorry, it seems you didn't enter a valid Ghanian mobile number. Please try again.", | |
page_token: pageObj.page_token | |
}); | |
return; | |
} | |
// Set originating network according to prefix | |
if (numberPrefix === '024' || numberPrefix === '054' || numberPrefix === '055') { | |
network = 'mtn'; | |
} else if (numberPrefix === '026' || numberPrefix === '056') { | |
network = 'airtel'; | |
} else if (numberPrefix === '027' || numberPrefix === '057') { | |
network = 'tigo'; | |
} | |
// Handle non-supported telco | |
if (!network) { | |
return sendTextMessage({ | |
recipientId: senderID, | |
messageText: | |
'Apologies, we only support MTN, Airtel and Tigo mobile money payments for now. Sorry for the inconvenience.', | |
page_token: pageObj.page_token | |
}); | |
} | |
// Set route option according to sending and receiving network | |
switch (network) { | |
case 'mtn': | |
route = 'rmta'; | |
break; | |
case 'tigo': | |
route = 'rtta'; | |
break; | |
case 'airtel': | |
route = 'rata'; | |
break; | |
} | |
// Handle failure in setting route option | |
if (!route) { | |
return sendTextMessage({ | |
recipientId: senderID, | |
messageText: | |
'We think an error has occurred. Kindly contact support.', | |
page_token: pageObj.page_token | |
}); | |
} | |
// Notify user to watch out for USSD prompt | |
sendTextMessage({ | |
recipientId: senderID, | |
messageText: | |
'You will be prompted to accept the transaction and enter your mobile money pin via USSD. Please note that you might incur additional charge from your telco as transaction cost.', | |
page_token: pageObj.page_token, | |
callback: () => { | |
sendTextMessage({ | |
recipientId: senderID, | |
messageText: | |
'Please note that you might incur additional charge from your telco as transaction cost.', | |
page_token: pageObj.page_token | |
}); | |
} | |
}); | |
// Query Mazzuma mobile money API | |
request({ | |
url: 'https://client.teamcyst.com/api_call.php', | |
headers: { 'content-type': 'application/json' }, | |
method: 'POST', | |
json: { | |
'price': orderObj.orderTotal, | |
'network': network, | |
'recipient_number': pageObj.mm_number, | |
'sender': orderObj.orderOwnerPhone, | |
'option': route, | |
'apikey': process.env.MAZZUMA_TOKEN | |
} | |
}, (error, response, body) => { | |
if (error) { return console.log('Error processing mobile money ', error); } | |
if (body && body.code === 1) { | |
// Payment successful. Send appropriate notification to user | |
sendTextMessage({ | |
recipientId: senderID, | |
messageText: `Payment has been received for order #${orderObj.orderNumber}. Your order is on the way!`, | |
page_token: pageObj.page_token | |
}); | |
} else { | |
console.log('payment error: ', error); | |
// Payment failure. notify user accordingly | |
return sendTextMessage({ | |
recipientId: senderID, | |
messageText: `Oops! An error occured and payment has NOT been received for order #${orderObj.orderNumber}. Please try again or contact support!`, | |
page_token: pageObj.page_token | |
}); | |
} | |
}); | |
}; | |
module.exports = processMobilemoney; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment