Obsługę płatności należy rozpocząć od zainstalowania frameworku Sails.js (instrukcja), utworzenia aplikacji Sails.js i zainstalowaniu w niej modułu Node.js – Billon:
sudo npm install billon --save
Po zainstalowaniu modułu obsługującego płatność w wygenerowanej przez „Sails.js” strukturze plików należy utworzyć w katalogu „/api/services/” plik (usługę) o nazwie np. „MyShop.js”. Plik „MyShop.js” powinien zaczynać się od zaimportowania biblioteki Billon oraz utworzenia obiektu płatności:
var billon = require('billon'); var myShop = new billon.Payment();
Następnie należy wywołać kolejno metody konfiguracyjne obiektu komponentu sklepowego, które otrzymując jako parametr „requestData” (jest to obiekt wysyłany jako zapytanie POST z przeglądarki użytkownika odwiedzającego stronę) zwrócą w callbacku do modułu Billon dane w formacie dostosowanym do węzła Corporate Node. Na końcu pliku należy utworzony obiekt komponentu sklepowego przypisać do pola „exports” obiektu „module”:
module.exports = myShop;
Metody konfiguracyjne
Metody konfiguracyjne zwracają referencję na swój obiekt. Przykład usługi zakupu produktu (płatności na produkt):
var billon = require("billon"); var myShop = new billon.Payment(); myShop.user(function (requestData, cb) { cb(requestData.user); }).products(function (requestData, cb) { cb(requestData.itemsId); }).product(function (requestData, itemId, cb) { if (itemId == 16) { // liczba tysięcznych części podstawowej jednostki monetarnej cb(2000); // 2 zł } else if (itemId == 17) { cb({ "amount_gross": 1.2 // 1,2 zł }); } else if (itemId == 18) { cb({ "item_id": "18", "item_name": "eBook", "quantity": 1, "unit_type": "szt", "unit_price_net": 0.24, "amount_net": 0.24, "amount_discount_net": 0, "vat": { "percent": 23, "amount": 0.06 }, "amount_gross": 0.3 // 0,3 zł }); } else if (itemId == 30) { KeyCode.find({}).exec(function (err, found) { return cb({ "item_id": "30", "item_name": "Kod dostępu", "quantity": 1, "unit_type": "szt", "unit_price_net": 0,81, "amount_net": 0,81, "amount_discount_net": 0, "vat": { "percent": 23, "amount": 0.19 }, "amount_gross": 1, KeyCodes: found}); }); } }).productBillon(function (requestData, item, cb) { // metoda niewymagana cb(item); }).productDelivery(function (requestData, items, cb) { // metoda niewymagana cb({ "delivery_id": "", "delivery_description": "", "delivery_kind": "", "delivery_price_net": "0", "vat": { "percent": "0", "amount": "0" }, "delivery_price_gross": "0", "estimated_delivery_date": "0" }); }).productDownload(function (requestData, items, delivery, document, cb) { if (items[0].item_id == 18) { cb(["http://sklep3.billon.net.pl/downloadFiles/eBook.pdf"]); } else if (items[0].item_id == 30) { var downloads = []; if (items[0] && items[0].KeyCodes && items[0].KeyCodes.length > 0) { var key = _.find(items[0].KeyCodes, function (keycode) { return keycode.Status === "available"; }); if (key) { downloads.push(key); return cb(downloads); } } cb({err: true}); } cb(false); }).currency(function (requestData, cb) { cb('EUR'); }).paymentTitle(function (requestData, items, delivery, cb) { cb('Tytuł płatności'); }).cNode(function (requestData, items, delivery, cb) { cb("eyJhkGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c3VyIjp7ImlkIjoyNSwic2FsdCI6IjEyZWVkMzQwLTM2NGUtMThNjgyN1hNDM5zA1MiOjEh0OTCJpYXQiDk0NjF9Q1M.zeWXG7Nyr3PFVhb4HRgLi5rh8fLZx41VHySHWUStIzc"); // API key użytkownika serwisu "billon.me" /*cb({ name: "billonpayments4", address: "http://213.189.38.34:20891" });*/ // komunikacja bezpośrednio z węzłem Corporate Node }).merchant(function (requestData, items, delivery, cb) { // metoda niewymagana cb({ "company": { "name": "Default", "NIP": "", "REGON": "", "KRS": "" }, "person": { "first_name": "Default", "last_name": "", "PESEL": "" }, "address": { "street": "Default", "city": "Default", "zip": "00-000", "country": "" }, "fax": "", "www": "", "email": "" }); }).documentParse(function (requestData, document, cb) { // metoda niewymagana cb(false); }).onFinishedPayment(function (items, delivery_option, document, requestData) { console.log(document); console.log(document.additionalInfo); console.log(document.taskId); // od wersji 2.3 modułu "billon" }).onRequestSend(function (requestName, parameters, requestResponse, error) { // zdarzenie wysłania operacji SOAP, metoda niewymagana console.log('Wiadomość SOAP: ', requestName); console.log('Parametry wiadomości: ', parameters); console.log('Odpowiedź węzła Corporate Node: ', requestResponse); console.log('Błąd przy wysyłaniu: ', error); }); module.exports = myShop;
Specyficznym rodzajem płatności zakupu produktu są płatności talerzykowe.
Przykład usługi przekazania datku (płatności na kwotę):
var billon = require("billon"); var myShop = new billon.Payment(); myShop.paymentTitle(function (requestData, items, delivery, cb) { return cb("Datek"); }).cNode(function (requestData, param1, param2, cb) { cb("eyJhkGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c3VyIjp7ImlkIjoyNSwic2FsdCI6IjEyZWVkMzQwLTM2NGUtMThNjgyN1hNDM5zA1MiOjEh0OTCJpYXQiDk0NjF9Q1M.zeWXG7Nyr3PFVhb4HRgLi5rh8fLZx41VHySHWUStIzc"); // API key użytkownika serwisu "billon.me" /*cb({ name: "billonpayments4", address: "http://213.189.38.34:20891" });*/ // komunikacja bezpośrednio z węzłem Corporate Node }).merchant(function (requestData, items, delivery, cb) { // metoda niewymagana return cb({ "company": { "name": "", "NIP": "", "REGON": "", "KRS": "" }, "person": { "first_name": "Default", "last_name": "", "PESEL": "" }, "address": { "street": "Default", "city": "Default", "zip": "00-000", "country": "" }, "fax": "", "www": "", "email": "" }); }).documentParse(function(requestData, document, cb) { // metoda niewymagana cb(document); }).onFinishedPayment(function (items, delivery_option, document, requestData) { console.log(document); console.log(document.additionalInfo); console.log(document.taskId); // od wersji 2.3 modułu "billon" }).onRequestSend(function (requestName, parameters, requestResponse, error) { // zdarzenie wysłania operacji SOAP, metoda niewymagana console.log('Wiadomość SOAP: ', requestName); console.log('Parametry wiadomości: ', parameters); console.log('Odpowiedź węzła Corporate Node: ', requestResponse); console.log('Błąd przy wysyłaniu: ', error); }); module.exports = myShop;
1. user
W funkcji będącej argumentem tej metody powinno być wywołanie funkcji „cb” z obiektem typu „peerLocation”. Obiekt ten powinien być automatycznie sprawdzaną przez JavaScript „billon-user.js” lokalizacją aplikacji użytkownika, jeśli jest ona uruchomiona na komputerze, lub w przeciwnym razie obiektem z jednym polem „userName” z nazwą użytkownika, którego dotyczyć ma transakcja z węzłem Corporate Node. Metoda nie dotyczy dotacji realizowanych za pomocą metody „donateInitiation”, gdzie obiekt typu „peerLocation” użytkownika jest pobierany z parametru „user” struktury JSON będącej parametrem żądania do kontrolera ze strony internetowej. W podobny sposób przekazywana jest kwota dotacji wyrażona w liczbie tysięcznej części podstawowej jednostki monetarnej poprzez parametr „amount” struktury JSON będącej parametrem żądania.
Przykład wywołania:
myShop.user(function (requestData, cb) { /* Przykładowa zawartość zmiennej requestData.user: { "userName": "test", // Wymagane ... // Pozostałe opcjonalne, dla szybszego wyszukania użytkownika w sieci. } */ cb(requestData.user); });
2. products
Metoda powinna przekazywać tablicę „id” produktów, których dotyczyć będzie transakcja. Nie dotyczy płatności datkowych. W wersji modułu 2.4 metoda nie jest wymagana.
Przykład wywołania:
myShop.products(function (requestData, cb) { /* Przykładowa zawartość zmiennej requestData.itemsId: [ 1, 2, 3 ] */ cb(requestData.itemsId); });
3. product
Funkcja użyta jako argument tej metody otrzymując dodatkowo parametr „id” pojedynczego produktu powinna przekazywać obiekt z danymi produktu lub cenę wyrażoną w liczbie tysięcznych częściach podstawowej jednostki monetarnej., np. pobranymi z bazy danych. Dane tego produktu będą dostępne dla użytkownika z poziomu przeglądarki w celu wyświetlania mu odpowiednich informacji o kupowanym produkcie. W przypadku nieużycia metody konfiguracyjnej „products” za pomocą tej metody należy ustawić pojedynczy produkt lub cenę.
Przykład wywołania:
myShop.product(function (requestData, itemId, cb) { if (itemId == 16) { // liczba tysięcznych części podstawowej jednostki monetarnej cb(2000); // 2 zł } else if (itemId == 17) { cb({ "amount_gross": 1.2 // 1,2 zł }); } else if (itemId === 26) { cb({ "item_id": "26", "item_name": "Obuwie", "quantity": 1, "unit_type": "szt", "unit_price_net": 64.96, "amount_net": 64.96, "amount_discount_net": 0, "vat": { "percent": 23, "amount": 14.94 }, "amount_gross": 79.9, "requestInvoiceData": "OPTIONAL", // opcjonalne dane do faktury "requestDeliveryData": "OBLIGATORY" // wymagane dane do dostawy // domyślna wartość w obu powyższych przypadkach: "NOT_NEEDED" - dane niepotrzebne }); } else if (itemId === 30) { KeyCode.find({Service: 'games'}).exec(function (err, found) { // sprzedaż kodów dostępu return cb({ "item_id": "30", "item_name": "Kod dostępu", "quantity": 1, "unit_type": "szt", "unit_price_net": 0,81, "amount_net": 0,81, "amount_discount_net": 0, "vat": { "percent": 23, "amount": 0.19 }, KeyCodes: found}); }); } });
Produktem może być unikatowy kod dostępu. Moduł „billon” potrafi samodzielnie wybierać kody i ustawić je jako użyte. Aby moć korzystać z kodów należy stworzyć model Sails.js „KeyCode”. Wymaganymi atrybutami tego modelu są:
- „Value” – kod;
- „Status” – status kodu, który może przyjmować wartości „available”, „sold” i „blocked”.
Przykład modelu „KeyCode.js”:
module.exports = { connection: 'database_connection', tableName: 'KeyCodes', autoPK: true, attributes: { Value: { type: "string", required: true, unique: true, size: 255 }, Status: { type: "string", enum: [ 'available' // kody możliwe do zakupu 'blocked', // kody zablokowane podczas trwania transakcji 'sold', // kody sprzedane 'used' // opcjonalna niestandardowa wartość uniemożliwiając zakup kodu ], defaultsTo: 'available' }, Service: { // opcjonalny niestandardowy atrybut kodu type: "string", size: 255 } } };
Zalecane jest samodzielne sprawdzanie dostępność kodów.
4. productBillon
Metoda „productBillon” różni się od poprzedniej tym, że przekazywany przez nią obiekt musi mieć konkretny format, dopasowany do API Billonu, aby poprawnie wyświetlać kupującemu np. dane fakturowe. Nie dotyczy płatności datkowych.
Przykład wywołania:
myShop.productBillon(function (requestData, item, cb) { // metoda niewymagana // Ponieważ w poprzedniej metodzie format był zgodny z API Billon, przesyłamy obiekt w niezmienionej formie. cb(item); });
5. productDelivery
W tej metodzie określony musi zostać wybrany przez użytkownika sposób dostawy. Metoda niewymagana. Nie dotyczy płatności za produkty online lub płatności datkowych.
Przykład wywołania:
myShop.productDelivery(function (requestData, items, cb) { // Poniżej struktura obiektu opcji dostawy. cb({ "delivery_id": "", "delivery_description": "", "delivery_kind": "", "delivery_price_net": "0", "vat": { "percent": "0", "amount": "0" }, "delivery_price_gross": "0", "estimated_delivery_date": "0" }); });
6. productDownload
Metoda dotyczy produktów pobieralnych i kodów. W przypadku produktów do pobrania należy w metodzie określić, jaki link ma trafić do użytkownika kupującego po zapłaceniu przez niego za daną paczkę (parametr „items”) produktów. W przypadku kodów dostępu należy użyć kodu znajdującego się poniżej. Kody i produkty pobieralne obsługuje skrypt „billon-loadingBar.js”, ale nie obsługuje skrypt „billon-paymentDialog.js” i skrypt „billon-plates.js” przeznaczony dla płatności talerzykowych.
Przykład wywołania:
myShop.productDownload(function (requestData, items, delivery, document, cb) { // Poniżej przykład, w którym produkt o "id" 18 jest pobieralny ze stałego linku, // a produkt o "id" 30 jest zwracany jako kod if (items[0].item_id == 18) { return cb(["http://sklep3.billon.net.pl/downloadFiles/eBook.pdf"]); } else if (items[0].item_id == 30) { var downloads = []; if (items[0] && items[0].KeyCodes && items[0].KeyCodes.length > 0) { var key = _.find(items[0].KeyCodes, function (keycode) { return keycode.Status === "available"; }); if (key) { downloads.push(key); // UsedKeyCode = key.Value; - zapisanie kodu do utworzonej wcześniej zmiennej return cb(downloads); } } cb({err: true}); } cb(false); });
7. currency
W tej metodzie można określić walutę płatności poprzez podanie jej kodu ISO 4217. Metoda jest opcjonalna. Domyślną walutą jest „PLN”.
Przykład wywołania:
myShop.currency(function (requestData, cb) { cb('EUR'); });
8. paymentTitle
Metoda ta służy do określania tytułu płatności.
Przykład wywołania:
myShop.paymentTitle(function (requestData, items, delivery, cb) { cb("Kod"); });
9. cNode
W tej metodzie należy okrelić, jaki węzeł Corporate Node ma obsługiwać dane płatności. Można podać API key użytkownika serwisu „billon.me”, który można skopiować ze strony ustawień, lub obiekt z nazwą użytkownika, na którego zalogowany jest węzeł Corporate Node, i adres, po którym jest dostępna komunikacja z nim. Można w tej metodzie zaszyć np. loadbalancing.
Przykład wywołania:
myShop.cNode(function (requestData, items, delivery, cb) { cb("eyJhkGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c3VyIjp7ImlkIjoyNSwic2FsdCI6IjEyZWVkMzQwLTM2NGUtMThNjgyN1hNDM5zA1MiOjEh0OTCJpYXQiDk0NjF9Q1M.zeWXG7Nyr3PFVhb4HRgLi5rh8fLZx41VHySHWUStIzc"); // API key użytkownika serwisu "billon.me" /*cb({ name: "billonpayments4", address: "http://213.189.38.34:20891" });*/ // obiekt zawierający nazwę oraz adres IP (z portem) węzła Corporate Node - komunikacja bezpośrednio z węzłem Corporate Node });
10. merchant
Ta metoda przekazuje dane, które ukażą się w aplikacji użytkownika jako dane sprzedawcy. Muszą mieć one format dostosowany do API Billonu. Metoda jest opcjonalna.
Przykład wywołania:
myShop.merchant(function (requestData, items, delivery, cb) { cb({ "company": { "name": "Default", "NIP": "", "REGON": "", "KRS": "" }, "person": { "first_name": "Default", "last_name": "", "PESEL": "" }, "address": { "street": "Default", "city": "Default", "zip": "00-000", "country": "" }, "fax": "", "www": "", "email": "" }); });
11. documentParse
Metoda documentParse jest miejscem do niestandardowego przetwarzania dokumentu płatności. Metoda jest opcjonalna.
myShop.documentParse(function (requestData, document, cb) { // Jeśli wywołuje metodę "cb" z argumentem "false", dokument nie będzie parsowany. cb(false); });
12. merchantBrandName
Metoda ta ustawia nazwę brandu, który domyślnie jest domeną strony, na której wykonywana jest płatność:
myShop.merchantBrandName(function(requestData, cb) { cb('Nazwa brandu'); });
13. plates
Za pomocą tej metody można ustawić ceny talerzyków w kolejności rosnącej. Ceny te mają zastosowanie w czasie wywołania w kontrolerze Sails.js metod „platePaymentSeriesStart” i „tariff”. Przykład użycia z wcześniej ustawionymi wartościami:
myShop.plates(function(requestData, cb) { cb(alpha * 1000, beta * 1000, gamma * 1000, delta * 1000, epsilon * 1000); // pomnożenie cen do liczby tysięcznych części podstawowej jednostki monetarnej }); // można wywołać metodę "cb" z mniejszą liczbą argumentów, talerzyk można usunąć również z taryfy podając liczbę 0
14. maxTotalAmount
Ta metoda służy do ustawiania maksymalnej sumy płatności talerzykowych w serii. Metoda ma zastosowanie w czasie wywołania w kontrolerze Sails.js metody „platePaymentSeriesStart”. Sumę wyraża się w liczbie tysięcznych części podstawowej jednostki monetarnej:
myShop.maxTotalAmount(function(requestData, cb) { cb(10000); // np. 10 zł })
15. duration
Za pomocą tej metody trzeba ustawić czas trwania serii płatności talerzykowych w sekundach. Metoda ma zastosowanie w czasie wywołania w kontrolerze Sails.js metody „platePaymentSeriesStart”. Przykład:
myShop.duration(function(requestData, cb) { cb(3600); })
16. maxPaymentsInSeries
Tą metodę można wywołać z argumentem w postaci funkcji, która wywołuję funkcję z jej drugiego argumentu z maksymalną liczbą płatności w serii płatności talerzykowych. Metoda ma zastosowanie w czasie wywołania w kontrolerze Sails.js metody „platePaymentSeriesStart”. Przykład:
myShop.maxPaymentsInSeries(function(requestData, cb) { cb(10); })
17. platePayment
Metoda ta służy do zdefiniowania płatności jako talerzykowej. Argumentem tej metody jest funkcja, która wywołuje funkcję z jej pierwszego argumentu z dwoma argumentami: identyfikatorem serii i rodzajem talerzyka („PLATE_ALPHA”, „PLATE_BETA”, „PLATE_GAMMA”, „PLATE_DELTA” lub „PLATE_EPSILON”). Przykład z użyciem wartości ustawionych skrypcie w przeglądarce:
myShop.platePayment(function(requestData, cb) { cb(requestData.seriesId, requestData.type); });
Uwaga! Rodzaj talerzyka może być zmieniony przez skrypt po stronie przeglądarki „billon-plates.js”, jeśli rodzajów talerzyków jest mniej niż pięć.
18. onReceivedPlatePaymentSeriesStatus
Metoda służy do obsługi statusów zwracanych przez węzłem Corporate Node w trakcie operacji akceptacji serii płatności talerzykowych. Metoda ma zastosowanie w czasie wywołania w kontrolerze Sails.js metody „platePaymentSeriesStart”
myShop.onReceivedPlatePaymentSeriesStatus(function (response) { console.log(response); // wiadomość zwrócona przez węzeł Corporate Node console.log(response.status); // zwracany status });
19. onAcceptedPlatePaymentSeries
Za pomocą tej metody można obsłużyć po stronie serwera zdarzenie akceptacji serii płatności talerzykowych. Metoda ma zastosowanie w czasie wywołania w kontrolerze Sails.js metody „platePaymentSeriesStart”
myShop.onAcceptedPlatePaymentSeries(function (paymentData) { // tutaj obsługa zdarzenia akceptacji serii płatności });
20. onFinishedPayment
Najistotniejsza dla integracji metoda. Określa co ma się stać, gdy węzeł Corporate Node odnotuje zakończoną sukcesem zainicjowaną wcześniej płatność. W standardowym przypadku może to być np. wysłanie wiadomości e-mail lub przekazanie informacji dalej zapytaniem POST do komponentu odpowiedzialnego za realizację zamówień.
Przykład wywołania:
myShop.onFinishedPayment(function (items, delivery, purchaseDocument, requestData) { // "purchaseDocument" to obiekt zawierający wszystkie dane fakturowe dotyczące transakcji, która zakończyła się sukcesem. console.log(purchaseDocument); if (purchaseDocument.additionalInfo) { var additionalInfo = JSON.parse(purchaseDocument.additionalInfo); if (additionalInfo.actual_amount) { console.log(additionalInfo.actual_amount); // faktyczna kwota zapłacona przez użytkownika } // console.log(UsedKeyCode); - użyta wcześniej zmienna, np. zakupiony kod } if (purchaseDocument.taskId) { console.log(purchaseDocument.taskId); // identyfikator transakcji w module "billon" od wersji 2.3 } });
21. onRequestSend
Metoda ta obsługuje zdarzenie wysłania wiadomości SOAP do węzła Corporate Node i otrzymania lub nieotrzymania odpowiedzi. Metoda może być przydatna do stworzenia logów komunikacji SOAP lub wykonania jakieś operacji przy niepowodzeniu płatności.
Przykład wywołania:
myShop.onRequestSend(function (requestName, parameters, requestResponse, error) { console.log('Wiadomość SOAP: ', requestName); console.log('Parametry wiadomości: ', parameters); console.log('Odpowiedź węzła Corporate Node: ', requestResponse); console.log('Błąd przy wysyłaniu: ', error); // błędy z węzła Corporate Node przekazywane są przez argument "requestResponse" });
Komunikacja z użytkownikiem WWW
Kiedy już posiadamy skonfigurowany serwer Sails.js, aby mógł się on skomunikować z użytkownikiem strony WWW, trzeba utworzyć kontroler:
sails generate controller Payment
W folderze „api/controllers” powstanie plik „PaymentController.js”, w którym należy obsłużyć komunikację do danego adresu i powiązać ją z usługą „MyShop.js”, którą wcześniej utworzyliśmy.
Poniżej przykład pliku „PaymentController.js”, który obsługuje inicjację płatności, śledzenie stanu transakcji dla użytkownika www oraz płatności datkowe.
module.exports = { // Obsługa inicjacji płatności na produkt. initiate: function(req, res) { MyShop.paymentInitiation(req, res); }, // Odczyt statusu transakcji dla strony www. status: function(req, res) { MyShop.paymentStatus(req.body.taskId || req.query.taskId, function(response) { res.json(response); }); }, // Obsługa płatności na kwotę. donate: function(req, res) { MyShop.donateInitiation(req, res); } };
Z tak opisanym kontrolerem użytkownik za pośrednictwem skryptów dla przeglądarek „billon-<moduł>.js” będzie mógł wykonywać zapytania HTTP „/payment/initiate/”, „/payment/status/” oraz „/payment/donate/” typu POST lub przez WebSocket.