diff --git a/README.md b/README.md index 5bbdf8e..392c9f2 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,14 @@ You can see the API the gateway intends to fulfill by looking at the [BaseGatewa **Note:** All builds are run daily thanks to [Travis CI cron jobs](https://docs.travis-ci.com/user/cron-jobs/). +- Omise +- Stripe +- Braintree +- PayPal +- WorldPay +- Beanstream +- Payeezy + [![Build Status](https://travis-ci.org/continuous-software/42-cent-braintree.svg?branch=master)](https://travis-ci.org/continuous-software/42-cent-braintree) [Braintree](https://github.com/continuous-software/42-cent-braintree) [![Build Status](https://travis-ci.org/continuous-software/42-cent-omise.svg?branch=master)](https://travis-ci.org/continuous-software/42-cent-omise) [Omise](https://github.com/continuous-software/42-cent-omise) [![Build Status](https://travis-ci.org/continuous-software/42-cent-stripe.svg?branch=master)](https://travis-ci.org/continuous-software/42-cent-stripe) [Stripe](https://github.com/continuous-software/42-cent-stripe) @@ -52,3 +60,22 @@ Feel free to go and fix things if you can. [![Build Status](https://travis-ci.org/continuous-software/node-payflow.svg?branch=master)](https://travis-ci.org/continuous-software/node-payflow) [Payflow](https://github.com/continuous-software/node-payflow) [![Build Status](https://travis-ci.org/continuous-software/node-rocketgate.svg?branch=master)](https://travis-ci.org/continuous-software/node-rocketgate) [RocketGate](https://github.com/continuous-software/node-rocketgate) [![Build Status](https://travis-ci.org/continuous-software/node-virtualmerchant.svg?branch=master)](https://travis-ci.org/continuous-software/node-virtualmerchant) [VirtualMerchant](https://github.com/continuous-software/node-virtualmerchant) + +## Gateway Specific Configuration + +### Payeezy + +```javascript +var client = Gateways.use('Payeezy', { + apiKey: 'your-api-key', + apiSecret: 'your-api-secret', + merchantToken: 'your-merchant-token', + environment: 'sandbox' // or 'production' +}); +``` + +Required configuration: +- `apiKey`: Your Payeezy API key +- `apiSecret`: Your Payeezy API secret +- `merchantToken`: Your Payeezy merchant token +- `environment`: Either 'sandbox' or 'production' (default: 'sandbox') diff --git a/index.js b/index.js index df325c4..78dd125 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ var PayPal = require('42-cent-paypal').factory; var WorldPay = require('42-cent-worldpay').factory; var Beanstream = require('42-cent-beanstream').factory; var Moneris = require('42-cent-moneris'); +var Payeezy = require('./lib/PayeezyGateway').factory; // Deprecated: // var AuthorizeNet = require('authorize-net'); @@ -22,6 +23,7 @@ var supportedGateway = { "PayPal": PayPal, "WorldPay": WorldPay, "Beanstream": Beanstream, + "Payeezy": Payeezy, // "Authorize.Net": AuthorizeNet, // "PayFlow": PayFlow, // "RocketGate": RocketGate, diff --git a/lib/PayeezyGateway.js b/lib/PayeezyGateway.js new file mode 100644 index 0000000..a2f57dc --- /dev/null +++ b/lib/PayeezyGateway.js @@ -0,0 +1,137 @@ +'use strict'; + +const BaseGateway = require('42-cent-base').BaseGateway; +const CreditCard = require('42-cent-model').CreditCard; +const Prospect = require('42-cent-model').Prospect; +const Promise = require('bluebird'); +const _ = require('lodash'); +const crypto = require('crypto'); + +class PayeezyGateway extends BaseGateway { + constructor(options) { + super(); + this.apiKey = options.apiKey; + this.apiSecret = options.apiSecret; + this.merchantToken = options.merchantToken; + this.environment = options.environment || 'sandbox'; + this.baseUrl = this.environment === 'sandbox' + ? 'https://api-cert.payeezy.com/v1' + : 'https://api.payeezy.com/v1'; + } + + generateHmac(payload, timestamp) { + const message = this.apiKey + timestamp + this.merchantToken + JSON.stringify(payload); + return crypto + .createHmac('sha256', this.apiSecret) + .update(message) + .digest('hex'); + } + + async submitTransaction(transaction) { + const timestamp = new Date().getTime(); + const hmac = this.generateHmac(transaction, timestamp); + + const headers = { + 'Content-Type': 'application/json', + 'apikey': this.apiKey, + 'token': this.merchantToken, + 'timestamp': timestamp, + 'hmac': hmac + }; + + try { + const response = await fetch(`${this.baseUrl}/transactions`, { + method: 'POST', + headers: headers, + body: JSON.stringify(transaction) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return this.createTransactionResponse(data); + } catch (error) { + throw new Error(`Failed to process transaction: ${error.message}`); + } + } + + createTransactionResponse(response) { + return { + transactionId: response.transaction_id, + _original: response, + authCode: response.authorization_num, + status: response.transaction_status + }; + } + + submitTransaction(order, creditCard, prospect, other) { + const payload = { + merchant_ref: order.orderId || Date.now().toString(), + transaction_type: 'purchase', + method: 'credit_card', + amount: order.amount, + currency_code: order.currency || 'USD', + credit_card: { + type: this.getCardType(creditCard.cardNumber), + cardholder_name: creditCard.cardHolder, + card_number: creditCard.cardNumber, + exp_date: creditCard.expirationMonth.toString().padStart(2, '0') + creditCard.expirationYear.toString().slice(-2), + cvv: creditCard.cvv + } + }; + + if (prospect) { + payload.billing_address = { + name: prospect.firstName + ' ' + prospect.lastName, + street: prospect.address1, + city: prospect.city, + state_province: prospect.state, + zip_postal_code: prospect.zip, + country: prospect.country || 'US' + }; + } + + return this.submitTransaction(payload); + } + + getCardType(number) { + // Basic card type detection + if (/^4/.test(number)) return 'visa'; + if (/^5[1-5]/.test(number)) return 'mastercard'; + if (/^3[47]/.test(number)) return 'amex'; + if (/^6(?:011|5)/.test(number)) return 'discover'; + return 'unknown'; + } + + refundTransaction(transactionId, options) { + const payload = { + merchant_ref: options.orderId || Date.now().toString(), + transaction_type: 'refund', + method: 'credit_card', + amount: options.amount, + currency_code: options.currency || 'USD', + transaction_tag: options.transactionTag, + transaction_id: transactionId + }; + + return this.submitTransaction(payload); + } + + voidTransaction(transactionId, options) { + const payload = { + merchant_ref: options.orderId || Date.now().toString(), + transaction_type: 'void', + method: 'credit_card', + transaction_tag: options.transactionTag, + transaction_id: transactionId + }; + + return this.submitTransaction(payload); + } +} + +exports.factory = function (options) { + return new PayeezyGateway(options); +}; diff --git a/package.json b/package.json index 8e5ce54..ad8f14f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "nmi": "^1.0.3", "payflow": "^1.0.0", "rocketgate": "^1.0.0", - "virtualmerchant": "^1.0.0" + "virtualmerchant": "^1.0.0", + "42-cent-payeezy": "^1.0.0" }, "devDependencies": { "mocha": "^1.21.4"