├── README.md ├── composer.json ├── js ├── connect.js └── widget.js └── lib └── XPaymentsCloud ├── ApiException.php ├── Client.php ├── Model ├── Payment.php └── Subscription.php ├── Request.php ├── Response.php └── Signature.php /README.md: -------------------------------------------------------------------------------- 1 | # X-Payments Cloud SDK for PHP 7.x 2 | 3 | When processing online credit card payments, X-Payments works as an intermediary between a shopping cart application or any other API-friendly system on one side and payment gateways and 3D-Secure systems on the other side, implementing the PCI compliant secure layer that works with credit card data and keeps your applications and systems out of the PCI scope. 4 | 5 | Refer to the [X-Payments Cloud API](https://xpayments.stoplight.io/docs/server-side-api/) for details. 6 | 7 | ### Supported payment gateways 8 | X-Payments Cloud supports more than 60 [payment gateway integrations](https://www.x-payments.com/help/XP_Cloud:Supported_payment_gateways): ANZ eGate, American Express Web-Services API Integration, Authorize.Net, Bambora (Beanstream), Beanstream (legacy API), Bendigo Bank, BillriantPay, BluePay, BlueSnap Payment API (XML), Braintree, BluePay Canada (Caledon), Cardinal Commerce Centinel, Chase Paymentech, CommWeb - Commonwealth Bank, BAC Credomatic, CyberSource - SOAP Toolkit API, X-Payments Demo Pay, X-Payments Demo Pay 3-D Secure, DIBS, DirectOne - Direct Interface, eProcessing Network - Transparent Database Engine, SecurePay Australia, Moneris eSELECTplus, Elavon (Realex API), ePDQ MPI XML (Phased out), eWAY Rapid - Direct Connection, eWay Realtime Payments XML, Sparrow (5th Dimension Gateway), First Data Payeezy Gateway (ex- Global Gateway e4), Global Iris, Global Payments, GoEmerchant - XML Gateway API, HeidelPay, Innovative Gateway, iTransact XML, Payment XP (Meritus) Web Host, NAB - National Australia Bank, NMI (Network Merchants Inc.), Netbilling - Direct Mode, Netevia, Ingenico ePayments (Ogone e-Commerce), PayGate South Africa, Payflow Pro, PayPal REST API, PayPal Payments Pro (PayPal API), PayPal Payments Pro (Payflow API), PSiGate XML API, QuantumGateway - XML Requester, Intuit QuickBooks Payments, QuickPay, Worldpay Corporate Gateway - Direct Model, Global Payments (ex. Realex), Opayo Direct (ex. Sage Pay Go - Direct Interface), Paya (ex. Sage Payments US), Simplify Commerce by MasterCard, SkipJack, Suncorp, TranSafe, powered by Monetra, 2Checkout, USA ePay - Transaction Gateway API, Elavon Converge (ex VirtualMerchant), WebXpress, Worldpay Total US, Worldpay US (Lynk Systems). 9 | 10 | ### Supported Fraud-screening services: 11 | - Kount 12 | - NoFraud 13 | - Signifyd 14 | 15 | ### Support 16 | If you have any questions please free to [contact us](https://www.x-payments.com/contact-us). 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xpayments/cloud-sdk-php", 3 | "type": "library", 4 | "description": "X-Payments Cloud PHP SDK", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "X-Payments", 9 | "homepage": "https://www.x-payments.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "~7.1.3||~7.2.0||~7.3.0||~7.4.0||^8.0", 14 | "ext-curl": "*", 15 | "ext-hash": "*", 16 | "ext-openssl": "*" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "XPaymentsCloud\\": "lib/XPaymentsCloud" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /js/connect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * X-Payments Cloud SDK - Connect Widget 3 | */ 4 | 5 | function XPaymentsConnect(elmSelector, quickAccessKey, handlers) { 6 | this.jsApiVersion = '2.0'; 7 | this.serverDomain = 'xpayments.com'; 8 | this.messageNamespace = 'xpayments.connect.'; 9 | 10 | this.previousHeight = -1; 11 | 12 | this.config = { 13 | debug: false, 14 | account: '', 15 | container: '', 16 | topElement: '', 17 | referrerUrl: document.location.href, 18 | applePayOnly: false, 19 | quickAccessKey: '' 20 | } 21 | 22 | this.handlers = {}; 23 | 24 | this.bindedListener = false; 25 | 26 | } 27 | 28 | XPaymentsConnect.prototype.init = function(settings) 29 | { 30 | for (var key in settings) { 31 | if ('undefined' !== typeof this.config[key]) { 32 | this.config[key] = settings[key]; 33 | } 34 | } 35 | 36 | // Set default handlers 37 | this.on('alert', function(params) { 38 | window.alert(params.message); 39 | }).on('config', function() { 40 | console.error('X-Payments Widget is not configured properly!'); 41 | }); 42 | 43 | this.bindedListener = this.messageListener.bind(this); 44 | window.addEventListener('message', this.bindedListener); 45 | 46 | return this; 47 | } 48 | 49 | XPaymentsConnect.prototype.getContainerElm = function() 50 | { 51 | return this.safeQuerySelector(this.config.container); 52 | } 53 | 54 | XPaymentsConnect.prototype.getIframeId = function() 55 | { 56 | return 'xpayments-connect'; 57 | } 58 | 59 | XPaymentsConnect.prototype.getIframeElm = function() 60 | { 61 | return document.getElementById(this.getIframeId()); 62 | } 63 | 64 | XPaymentsConnect.prototype.safeQuerySelector = function(selector) 65 | { 66 | var elm = false; 67 | if (selector) { 68 | elm = document.querySelector(selector); 69 | } 70 | return elm; 71 | } 72 | 73 | XPaymentsConnect.prototype.resize = function(height) 74 | { 75 | var elm = this.getIframeElm(); 76 | if (elm) { 77 | this.previousHeight = elm.style.height; 78 | elm.style.height = height + 'px'; 79 | } 80 | } 81 | 82 | XPaymentsConnect.prototype.load = function() 83 | { 84 | var containerElm = this.getContainerElm(); 85 | if (!containerElm) { 86 | return this; 87 | } 88 | 89 | var elm = document.createElement('iframe'); 90 | elm.id = this.getIframeId(); 91 | elm.style.width = '100%'; 92 | elm.style.height = '0'; 93 | elm.style.overflow = 'hidden'; 94 | elm.setAttribute('scrolling', 'no') 95 | containerElm.appendChild(elm); 96 | 97 | elm.src = this.getRedirectUrl(); 98 | 99 | } 100 | 101 | XPaymentsConnect.prototype.getRedirectUrl = function() 102 | { 103 | return 'https://' + this.getServerHost() + '/' + 104 | '?ref=' + encodeURIComponent(this.config.referrerUrl) + 105 | '&account=' + encodeURIComponent(this.config.account) + 106 | '&api_version=' + encodeURIComponent(this.jsApiVersion) + 107 | '&quickaccess=' + encodeURIComponent(this.config.quickAccessKey); 108 | } 109 | 110 | XPaymentsConnect.prototype.on = function(event, handler) 111 | { 112 | this.handlers[event] = handler.bind(this); 113 | return this; 114 | } 115 | 116 | XPaymentsConnect.prototype.trigger = function(event, params) 117 | { 118 | if ('function' === typeof this.handlers[event]) { 119 | this.handlers[event](params); 120 | } 121 | return this; 122 | } 123 | 124 | XPaymentsConnect.prototype.getServerHost = function() 125 | { 126 | return 'connect.' + this.serverDomain; 127 | } 128 | 129 | XPaymentsConnect.prototype.messageListener = function(event) 130 | { 131 | if (window.JSON) { 132 | var msg = false; 133 | 134 | try { 135 | msg = window.JSON.parse(event.data); 136 | } catch (e) { 137 | // Skip invalid messages 138 | } 139 | 140 | if (msg && msg.event && 0 === msg.event.indexOf(this.messageNamespace)) { 141 | this.log('X-Payments Event: ' + msg.event + "\n" + window.JSON.stringify(msg.params)); 142 | 143 | var eventType = msg.event.substr(this.messageNamespace.length); 144 | 145 | if ('loaded' === eventType) { 146 | if (-1 !== this.previousHeight) { 147 | var topElm = (this.config.topElement) 148 | ? this.safeQuerySelector(this.config.topElement) 149 | : this.getContainerElm(); 150 | if (topElm) { 151 | topElm.scrollIntoView(true); 152 | } 153 | } 154 | this.resize(msg.params.height); 155 | } else if ('resize' === eventType) { 156 | this.resize(msg.params.height); 157 | } else if ('alert' === eventType) { 158 | msg.params.message = msg.params.message.replace(/<\/?[^>]+>/gi, ''); 159 | } 160 | 161 | this.trigger(eventType, msg.params); 162 | } 163 | } 164 | } 165 | 166 | XPaymentsConnect.prototype.postMessage = function(message) 167 | { 168 | var elm = this.getIframeElm(); 169 | if ( 170 | window.postMessage 171 | && window.JSON 172 | && elm 173 | && elm.contentWindow 174 | ) { 175 | this.log('Sent to X-Payments: ' + message.event + "\n" + window.JSON.stringify(message.params)); 176 | elm.contentWindow.postMessage(window.JSON.stringify(message), '*'); 177 | } else { 178 | this.log('Error sending message - iframe wasn\'t initialized!'); 179 | } 180 | } 181 | 182 | XPaymentsConnect.prototype.log = function(msg) { 183 | if (this.config.debug) { 184 | console.log(msg); 185 | } 186 | } -------------------------------------------------------------------------------- /js/widget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * X-Payments Cloud SDK - Payment Widget 3 | */ 4 | 5 | window.XPaymentsWidget = function() 6 | { 7 | this.jsApiVersion = '2.0'; 8 | this.serverDomain = 'xpayments.com'; 9 | this.messageNamespace = 'xpayments.widget.'; 10 | this.receiverNamespace = 'xpayments.checkout.'; 11 | this.widgetId = this.generateId(); 12 | this.previousHeight = -1; 13 | this.applePayData = { 14 | session: null, 15 | supportedNetworks: [], 16 | merchantCapabilities: [], 17 | merchantId: '', 18 | }; 19 | this.googlePayData = { 20 | client: null, 21 | libUrl: 'https://pay.google.com/gp/p/js/pay.js', 22 | libLoaded: false, 23 | paymentMethods: {}, 24 | merchantInfo: {}, 25 | tokenizationSpecification: {}, 26 | }; 27 | this.paymentMethod = null; 28 | 29 | this.config = { 30 | debug: false, 31 | account: '', 32 | widgetKey: '', 33 | container: '', 34 | form: '', 35 | language: '', 36 | customerId: '', 37 | tokenName: 'xpaymentsToken', 38 | showSaveCard: true, 39 | enableWallets: true, 40 | applePay: { 41 | enabled: false, 42 | shippingMethods: [], 43 | requiredShippingFields: [], 44 | requiredBillingFields: [], 45 | }, 46 | googlePay: { 47 | enabled: false, 48 | shippingMethods: [], 49 | requiredShippingFields: [], 50 | requiredBillingFields: [], 51 | }, 52 | walletMode: '', 53 | company: { 54 | name: '', 55 | domain: document.location.hostname, 56 | countryCode: '', 57 | }, 58 | order: { 59 | tokenizeCard: false, 60 | total: -1, 61 | currency: '' 62 | } 63 | }; 64 | 65 | this.handlers = {}; 66 | 67 | this.bindedListener = false; 68 | this.bindedSubmit = false; 69 | 70 | } 71 | 72 | XPaymentsWidget.prototype.on = function(event, handler, context) 73 | { 74 | if ('undefined' === typeof context) { 75 | context = this; 76 | } 77 | 78 | if ('formSubmit' !== event) { 79 | 80 | this.handlers[event] = handler.bind(context); 81 | 82 | } else { 83 | var formElm = this.getFormElm(); 84 | 85 | if (formElm) { 86 | if (this.bindedSubmit) { 87 | formElm.removeEventListener('submit', this.bindedSubmit); 88 | } 89 | this.bindedSubmit = handler.bind(context); 90 | formElm.addEventListener('submit', this.bindedSubmit); 91 | } 92 | } 93 | 94 | return this; 95 | } 96 | 97 | XPaymentsWidget.prototype.trigger = function(event, params) 98 | { 99 | var result = null; 100 | 101 | if ('function' === typeof this.handlers[event]) { 102 | result = this.handlers[event](params); 103 | } 104 | 105 | this._log('X-Payments widget triggered: ' + event, params); 106 | 107 | return result; 108 | } 109 | 110 | XPaymentsWidget.prototype.init = function(settings) 111 | { 112 | for (var key in settings) { 113 | if ('undefined' !== typeof this.config[key]) { 114 | if ('object' === typeof this.config[key]) { 115 | for (var subkey in settings[key]) { 116 | if ('undefined' !== typeof this.config[key][subkey]) { 117 | this.config[key][subkey] = settings[key][subkey]; 118 | } 119 | } 120 | } else { 121 | this.config[key] = settings[key]; 122 | } 123 | } 124 | } 125 | 126 | if (this.config.order.tokenizeCard) { 127 | this.config.showSaveCard = false; 128 | } 129 | 130 | // Set default handlers 131 | this.on('formSubmit', function (domEvent) { 132 | // "this" here is the widget 133 | if (this.isValid()) { 134 | this.submit(); 135 | domEvent.preventDefault(); 136 | } 137 | }) 138 | .on('success', this._defaultSuccessHandler) 139 | .on('applepay.paymentauthorized', this._applePayAuthorized) 140 | .on('applepay.buttonclick', function() { 141 | this.isValid() && this.submit(); 142 | }) 143 | .on('googlepay.paymentauthorized', this._googlePayAuthorized) 144 | .on('googlepay.buttonclick', function() { 145 | this.isValid() && this.submit(); 146 | }) 147 | .on('alert', function(params) { 148 | window.alert(params.message); 149 | }); 150 | 151 | this.bindedListener = this.messageListener.bind(this); 152 | window.addEventListener('message', this.bindedListener); 153 | 154 | if ( 155 | 'undefined' !== typeof settings.autoload 156 | && settings.autoload 157 | ) { 158 | this.load(); 159 | } 160 | 161 | return this; 162 | } 163 | 164 | XPaymentsWidget.prototype.generateId = function() 165 | { 166 | return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 167 | } 168 | 169 | XPaymentsWidget.prototype.getIframeId = function() 170 | { 171 | return 'xpayments-' + this.widgetId; 172 | } 173 | 174 | XPaymentsWidget.prototype.getIframeElm = function() 175 | { 176 | return document.getElementById(this.getIframeId()); 177 | } 178 | 179 | XPaymentsWidget.prototype.getContainerElm = function() 180 | { 181 | return this.safeQuerySelector(this.config.container); 182 | } 183 | 184 | XPaymentsWidget.prototype.getFormElm = function() 185 | { 186 | return this.safeQuerySelector(this.config.form); 187 | } 188 | 189 | XPaymentsWidget.prototype.isValid = function() 190 | { 191 | return this.getIframeElm() && this.getFormElm(); 192 | } 193 | 194 | XPaymentsWidget.prototype.safeQuerySelector = function(selector) 195 | { 196 | var elm = false; 197 | if (selector) { 198 | elm = document.querySelector(selector); 199 | } 200 | return elm; 201 | } 202 | 203 | XPaymentsWidget.prototype.loadAsyncJS = function(url, callback) 204 | { 205 | var script = document.createElement('script'); 206 | script.type = 'text/javascript'; 207 | script.async = true; 208 | script.onload = callback; 209 | script.src = url; 210 | document.getElementsByTagName('head')[0].appendChild(script); 211 | } 212 | 213 | XPaymentsWidget.prototype.load = function() 214 | { 215 | var containerElm = this.getContainerElm(); 216 | if (!containerElm) { 217 | return this; 218 | } 219 | 220 | var elm = this.getIframeElm(); 221 | if (!elm) { 222 | elm = document.createElement('iframe'); 223 | elm.id = this.getIframeId(); 224 | elm.style.width = '100%'; 225 | elm.style.height = '0'; 226 | elm.style.overflow = 'hidden'; 227 | elm.style.border = 'none'; 228 | if (this.config.walletMode) { 229 | elm.style.display = 'none'; 230 | } 231 | elm.setAttribute('scrolling', 'no'); 232 | containerElm.appendChild(elm); 233 | } 234 | 235 | var url = 236 | this.getServerUrl() + '/payment.php' + 237 | '?widget_key=' + encodeURIComponent(this.config.widgetKey) + 238 | '&widget_id=' + encodeURIComponent(this.widgetId) + 239 | '&shop=' + encodeURIComponent(this.config.company.domain) + 240 | '&api_version=' + encodeURIComponent(this.jsApiVersion); 241 | 242 | if (this.config.customerId) { 243 | url += '&customer_id=' + encodeURIComponent(this.config.customerId); 244 | } 245 | if (this.config.language) { 246 | url += '&language=' + encodeURIComponent(this.config.language); 247 | } 248 | if (this.config.walletMode) { 249 | url += '&target=wallet&mode=' + encodeURIComponent(this.config.walletMode); 250 | } 251 | elm.src = url; 252 | 253 | if (this._isGooglePayEnabled()) { 254 | this.loadAsyncJS(this.googlePayData.libUrl, (function() { 255 | this.googlePayData.libLoaded = true; 256 | }).bind(this)); 257 | } 258 | 259 | return this; 260 | } 261 | 262 | XPaymentsWidget.prototype.getServerHost = function() 263 | { 264 | return this.config.account + '.' + this.serverDomain; 265 | } 266 | 267 | XPaymentsWidget.prototype.getServerUrl = function() 268 | { 269 | return 'https://' + this.getServerHost(); 270 | } 271 | 272 | XPaymentsWidget.prototype.submit = function() 273 | { 274 | if (!this.config.walletMode) { 275 | this._sendEvent('submit'); 276 | } else { 277 | switch (this.getPaymentMethod()) { 278 | case 'applePay': 279 | this.trigger('applepay.start'); 280 | this._applePayStart(); 281 | break; 282 | case 'googlePay': 283 | this.trigger('googlepay.start'); 284 | this._googlePayStart(); 285 | break; 286 | } 287 | } 288 | } 289 | 290 | XPaymentsWidget.prototype.beginCheckoutWithWallet = function() 291 | { 292 | this.submit(); 293 | } 294 | 295 | XPaymentsWidget.prototype._afterLoad = function(params) 296 | { 297 | this.showSaveCard(); 298 | if (this._isApplePayAvailable()) { 299 | this._sendEvent('applepay.enable'); 300 | } 301 | if (this._isGooglePayEnabled()) { 302 | // Actual GPay availability can be checked only after loading 303 | this._sendGooglePayLoaded(); 304 | } 305 | this.setOrder(); 306 | this.resize(params.height); 307 | } 308 | 309 | XPaymentsWidget.prototype._defaultSuccessHandler = function(params) { 310 | var formElm = this.getFormElm(); 311 | if (formElm) { 312 | var input = document.getElementById(this.config.tokenName); 313 | if (!input) { 314 | input = document.createElement('input'); 315 | input.type = 'hidden'; 316 | input.name = input.id = this.config.tokenName; 317 | formElm.appendChild(input); 318 | } 319 | input.value = params.token; 320 | formElm.submit(); 321 | } 322 | } 323 | 324 | XPaymentsWidget.prototype.getPaymentMethod = function() 325 | { 326 | return this.config.walletMode || this.paymentMethod; 327 | } 328 | 329 | XPaymentsWidget.prototype._paymentMethodChange = function(params) 330 | { 331 | this.paymentMethod = params.newId; 332 | } 333 | 334 | XPaymentsWidget.prototype._applePayValidated = function(params) 335 | { 336 | try { 337 | this.applePayData.session.completeMerchantValidation(params.data); 338 | } catch (e) { 339 | } 340 | } 341 | 342 | XPaymentsWidget.prototype._applePayAuthorized = function(params) 343 | { 344 | this.succeedApplePayPayment(params); 345 | } 346 | 347 | XPaymentsWidget.prototype._applePayCompleted = function(params) 348 | { 349 | this.completeApplePayPayment({ status: ApplePaySession.STATUS_SUCCESS, errors: [] }); 350 | } 351 | 352 | XPaymentsWidget.prototype._applePayError = function(params) 353 | { 354 | try { 355 | this.applePayData.session.abort(); 356 | } catch (e) { 357 | // Skip errors if any 358 | } 359 | } 360 | 361 | XPaymentsWidget.prototype._applePayStart = function() 362 | { 363 | if (!this.applePayData.merchantCapabilities.length) { 364 | this._sendEvent('applepay.cancel', { alert: true }); 365 | return; 366 | } 367 | 368 | var request = { 369 | countryCode: this.config.company.countryCode, 370 | currencyCode: this.config.order.currency, 371 | supportedNetworks: this.applePayData.supportedNetworks, 372 | merchantCapabilities: this.applePayData.merchantCapabilities, 373 | total: { 374 | label: this.config.company.name, 375 | amount: this.config.order.total 376 | }, 377 | }; 378 | 379 | this.applePayCustomerAddress = null; 380 | if (this.config.walletMode) { 381 | if (this.config.applePay.shippingMethods.length) { 382 | request.shippingMethods = this.config.applePay.shippingMethods; 383 | } 384 | if (this.config.applePay.requiredShippingFields.length) { 385 | request.requiredShippingContactFields = this.config.applePay.requiredShippingFields; 386 | } 387 | if (this.config.applePay.requiredBillingFields.length) { 388 | request.requiredBillingContactFields = this.config.applePay.requiredBillingFields; 389 | } 390 | } 391 | 392 | this.applePayData.session = new ApplePaySession(3, request); 393 | 394 | this.applePayData.session.onvalidatemerchant = (function(event) { 395 | this._sendEvent('applepay.validatemerchant', { 396 | validationURL: event.validationURL, 397 | displayName: this.config.company.name, 398 | context: this.config.company.domain, 399 | }); 400 | }).bind(this); 401 | 402 | this.applePayData.session.onpaymentauthorized = (function(event) { 403 | this.trigger('applepay.paymentauthorized', event.payment); 404 | }).bind(this); 405 | 406 | this.applePayData.session.oncancel = (function(event) { 407 | var params = {}; 408 | if ('undefined' !== typeof event.sessionError) { 409 | params.error = event.sessionError; 410 | } 411 | this._sendEvent('applepay.cancel', params); 412 | this.trigger('applepay.cancel', params); 413 | }).bind(this); 414 | 415 | if (this.config.walletMode) { 416 | this.applePayData.session.onshippingcontactselected = (function(event) { 417 | this.trigger('applepay.shippingcontactselected', event.shippingContact); 418 | }).bind(this); 419 | this.applePayData.session.onshippingmethodselected = (function(event) { 420 | this.trigger('applepay.shippingmethodselected', event.shippingMethod); 421 | }).bind(this); 422 | } 423 | 424 | this.applePayData.session.begin(); 425 | 426 | } 427 | 428 | XPaymentsWidget.prototype._parseApplePayNewTotal = function(updateData) 429 | { 430 | this.setOrder(updateData.newTotal.amount); 431 | if ('undefined' != typeof updateData.newTotal && 'undefined' == typeof updateData.newTotal.label) { 432 | updateData.newTotal.label = this.config.company.name; 433 | } 434 | return updateData; 435 | } 436 | 437 | XPaymentsWidget.prototype.completeApplePayShippingContactSelection = function(updateData) { 438 | this.applePayData.session.completeShippingContactSelection(this._parseApplePayNewTotal(updateData)); 439 | } 440 | 441 | XPaymentsWidget.prototype.completeApplePayShippingMethodSelection = function(updateData) { 442 | this.applePayData.session.completeShippingMethodSelection(this._parseApplePayNewTotal(updateData)); 443 | } 444 | 445 | XPaymentsWidget.prototype.completeApplePayPayment = function(updateData) { 446 | this.applePayData.session.completePayment(updateData); 447 | } 448 | 449 | XPaymentsWidget.prototype.succeedApplePayPayment = function(payment) { 450 | this._sendEvent('applepay.paymentauthorized', { payment: payment }); 451 | } 452 | 453 | XPaymentsWidget.prototype.isApplePaySupportedByDevice = function() { 454 | return (window.ApplePaySession && ApplePaySession.canMakePayments()); 455 | } 456 | 457 | XPaymentsWidget.prototype._isApplePayAvailable = function() { 458 | return this.isApplePaySupportedByDevice() 459 | && ( 460 | this.config.enableWallets && this.config.applePay.enabled 461 | || 'applePay' === this.config.walletMode 462 | ); 463 | } 464 | 465 | XPaymentsWidget.prototype._checkApplePayActiveCard = function() 466 | { 467 | var promise = ApplePaySession.canMakePaymentsWithActiveCard(this.applePayData.merchantId); 468 | promise.then((function (canMakePayments) { 469 | if (canMakePayments) { 470 | this.trigger('applepay.forceselect'); 471 | this._sendEvent('applepay.select'); 472 | } 473 | }).bind(this)); 474 | } 475 | 476 | XPaymentsWidget.prototype._applePayInit = function(params) 477 | { 478 | this.applePayData.supportedNetworks = params.supportedNetworks; 479 | this.applePayData.merchantCapabilities = params.merchantCapabilities; 480 | this.applePayData.merchantId = params.merchantId; 481 | if (!this.config.walletMode) { 482 | this._checkApplePayActiveCard(); 483 | } 484 | } 485 | 486 | XPaymentsWidget.prototype._isGooglePayEnabled = function() { 487 | return ( 488 | this.config.enableWallets && this.config.googlePay.enabled 489 | || 'googlePay' === this.config.walletMode 490 | ); 491 | } 492 | 493 | XPaymentsWidget.prototype._sendGooglePayLoaded = function() 494 | { 495 | var promise = new Promise((function(resolve, reject) { 496 | var counter = 0; 497 | var checkReady = (function() { 498 | counter++; 499 | if (this.googlePayData.libLoaded) { 500 | resolve(); 501 | } else if (counter < 300) { 502 | setTimeout(checkReady, 100); 503 | } else { 504 | this._log('Error! Failed to load Google Pay library.') 505 | } 506 | }).bind(this); 507 | checkReady(); 508 | }).bind(this)); 509 | 510 | promise.then( 511 | (function() { 512 | this._sendEvent('googlepay.loaded', { origin: this.config.company.domain }); 513 | }).bind(this) 514 | ); 515 | } 516 | 517 | XPaymentsWidget.prototype._googlePayPrepareBaseRequest = function() 518 | { 519 | var baseRequest = { 520 | apiVersion: 2, 521 | apiVersionMinor: 0, 522 | allowedPaymentMethods: this.googlePayData.paymentMethods, 523 | } 524 | 525 | return baseRequest; 526 | } 527 | 528 | XPaymentsWidget.prototype._googlePayPrepareReadyToPay = function(existingRequired) 529 | { 530 | var request = {}; 531 | if ('undefined' !== typeof existingRequired && existingRequired) { 532 | request.existingPaymentMethodRequired = existingRequired; 533 | } 534 | 535 | return Object.assign({}, this._googlePayPrepareBaseRequest(), request); 536 | } 537 | 538 | XPaymentsWidget.prototype._googlePayPrepareLoadPayment = function(request) 539 | { 540 | var baseRequest = this._googlePayPrepareBaseRequest(); 541 | 542 | for (var key in baseRequest.allowedPaymentMethods) { 543 | baseRequest.allowedPaymentMethods[key].tokenizationSpecification = this.googlePayData.tokenizationSpecification; 544 | if (this.config.walletMode && this.config.googlePay.requiredBillingFields.length) { 545 | baseRequest.allowedPaymentMethods[key].parameters.billingAddressRequired = true; 546 | baseRequest.allowedPaymentMethods[key].parameters.billingAddressParameters = { 547 | format: (-1 !== this.config.googlePay.requiredBillingFields.indexOf('full')) ? 'FULL' : 'MIN', 548 | phoneNumberRequired: (-1 !== this.config.googlePay.requiredBillingFields.indexOf('phone')), 549 | } 550 | } 551 | 552 | } 553 | 554 | return Object.assign({}, baseRequest, request); 555 | } 556 | 557 | XPaymentsWidget.prototype._googlePayInit = function(params) 558 | { 559 | this.googlePayData.merchantInfo = { 560 | merchantName: this.config.company.name, 561 | merchantOrigin: this.config.company.domain, 562 | merchantId: params.businessId, 563 | }; 564 | if (params.authJwt) { 565 | this.googlePayData.merchantInfo.authJwt = params.authJwt; 566 | } 567 | 568 | this.googlePayData.tokenizationSpecification = { 569 | type: 'PAYMENT_GATEWAY', 570 | parameters: { 571 | gateway: params.gatewayId, 572 | gatewayMerchantId: params.merchantId, 573 | } 574 | }; 575 | 576 | var options = { 577 | environment: params.environment, 578 | merchantInfo: this.googlePayData.merchantInfo, 579 | paymentDataCallbacks: { 580 | onPaymentAuthorized: (function(paymentData) { 581 | return this.trigger('googlepay.paymentauthorized', paymentData, true); 582 | }).bind(this), 583 | } 584 | } 585 | 586 | if (this.config.walletMode && this.config.googlePay.requiredShippingFields.length) { 587 | options.paymentDataCallbacks.onPaymentDataChanged = (function(intermediatePaymentData) { 588 | return this.trigger('googlepay.paymentdatachanged', intermediatePaymentData); 589 | }).bind(this); 590 | } 591 | 592 | this.googlePayData.client = new google.payments.api.PaymentsClient(options); 593 | 594 | this.googlePayData.paymentMethods = []; 595 | for (var key in params.paymentMethods) { 596 | this.googlePayData.paymentMethods.push({ 597 | type: params.paymentMethods[key], 598 | parameters: { 599 | allowedAuthMethods: params.authMethods, 600 | allowedCardNetworks: params.supportedNetworks 601 | } 602 | }); 603 | } 604 | 605 | var request = this._googlePayPrepareReadyToPay(); 606 | 607 | this.googlePayData.client.isReadyToPay(request) 608 | .then((function(response) { 609 | if (response.result) { 610 | this._sendEvent('googlepay.enable'); 611 | this.trigger('googlepay.ready'); 612 | } else { 613 | this.trigger('googlepay.nonready'); 614 | } 615 | }).bind(this)) 616 | .catch((function(err) { 617 | this._log(err); 618 | }).bind(this)); 619 | } 620 | 621 | XPaymentsWidget.prototype._googlePayStart = function(params) 622 | { 623 | if (!this.googlePayData.client) { 624 | this._sendEvent('googlepay.cancel', { alert: true }); 625 | return; 626 | } 627 | 628 | var request = { 629 | merchantInfo: this.googlePayData.merchantInfo, 630 | transactionInfo: { 631 | totalPriceStatus: 'FINAL', 632 | totalPrice: this.config.order.total.toString(), 633 | currencyCode: this.config.order.currency, 634 | countryCode: this.config.company.countryCode, 635 | }, 636 | callbackIntents: [ 637 | 'PAYMENT_AUTHORIZATION', 638 | ], 639 | }; 640 | 641 | if (this.config.walletMode && this.config.googlePay.requiredShippingFields.length) { 642 | // We need to add displayItems because them won't show after finalization otherwise 643 | request.transactionInfo.displayItems = [ 644 | { 645 | label: 'Subtotal', 646 | type: 'SUBTOTAL', 647 | price: this.config.order.total.toString(), 648 | } 649 | ]; 650 | request.transactionInfo.totalPriceStatus = 'ESTIMATED'; 651 | request.transactionInfo.totalPriceLabel = 'Total'; 652 | 653 | request.callbackIntents.push('SHIPPING_ADDRESS'); 654 | request.shippingAddressRequired = true; 655 | request.shippingAddressParameters = { 656 | phoneNumberRequired: (-1 !== this.config.googlePay.requiredShippingFields.indexOf('phone')) 657 | }; 658 | request.emailRequired = (-1 !== this.config.googlePay.requiredShippingFields.indexOf('email')); 659 | 660 | if (this.config.googlePay.shippingMethods.length) { 661 | request.callbackIntents.push('SHIPPING_OPTION'); 662 | request.shippingOptionRequired = true; 663 | } 664 | } 665 | 666 | request = this._googlePayPrepareLoadPayment(request); 667 | 668 | this.googlePayData.client.loadPaymentData(request) 669 | .then((function(paymentData) { 670 | // Successful response parsed in googlepay.paymentauthorized 671 | }).bind(this)) 672 | .catch((function(err) { 673 | this._sendEvent('googlepay.cancel', { error: err }); 674 | this.trigger('googlepay.cancel'); 675 | }).bind(this)); 676 | } 677 | 678 | XPaymentsWidget.prototype._googlePayAuthorized = function(paymentData) 679 | { 680 | return new Promise((function(resolve, reject) { 681 | resolve(this.succeedGooglePayPayment(paymentData)); 682 | }).bind(this)); 683 | } 684 | 685 | XPaymentsWidget.prototype._googlePayError = function(params) 686 | { 687 | // Do nothing 688 | } 689 | 690 | XPaymentsWidget.prototype.succeedGooglePayPayment = function(paymentData) { 691 | this._sendEvent('googlepay.paymentauthorized', { payment: paymentData.paymentMethodData }); 692 | return { transactionState: 'SUCCESS' }; 693 | } 694 | 695 | XPaymentsWidget.prototype.isGooglePayInitialized = function(options) 696 | { 697 | return (null !== this.googlePayData.client); 698 | } 699 | 700 | XPaymentsWidget.prototype.createApplePayButton = function(options) 701 | { 702 | if (!this.isApplePaySupportedByDevice()) { 703 | console.error('Apple Pay is not supported by the device'); 704 | return null; 705 | } 706 | 707 | var buttonOptions = { 708 | onClick: (function() { 709 | this.trigger('applepay.buttonclick'); 710 | }).bind(this), 711 | wrapperClass: 'apple-pay-button-wrapper', 712 | buttonClass: 'apple-pay-button', 713 | buttonContent: '', 714 | } 715 | 716 | if ('undefined' !== typeof options) { 717 | buttonOptions = Object.assign({}, buttonOptions, options); 718 | } 719 | 720 | var wrapper = document.createElement('div') 721 | wrapper.className = buttonOptions.wrapperClass; 722 | 723 | var button = document.createElement('button') 724 | button.className = buttonOptions.buttonClass; 725 | button.type = 'button'; 726 | button.innerHTML = buttonOptions.buttonContent; 727 | button.addEventListener('click', buttonOptions.onClick); 728 | 729 | wrapper.appendChild(button); 730 | 731 | return wrapper; 732 | } 733 | 734 | 735 | XPaymentsWidget.prototype.createGooglePayButton = function(options) 736 | { 737 | if (!this.isGooglePayInitialized()) { 738 | console.error('Google Pay not initalized'); 739 | return null; 740 | } 741 | 742 | var buttonOptions = { 743 | onClick: (function() { 744 | this.trigger('googlepay.buttonclick'); 745 | }).bind(this) 746 | } 747 | 748 | if ('undefined' !== typeof options) { 749 | buttonOptions = Object.assign({}, buttonOptions, options); 750 | } 751 | 752 | return this.googlePayData.client.createButton(buttonOptions); 753 | } 754 | 755 | XPaymentsWidget.prototype.setWalletMode = function(walletId) 756 | { 757 | this.config.walletMode = walletId; 758 | } 759 | 760 | XPaymentsWidget.prototype.showSaveCard = function(value) 761 | { 762 | if ('undefined' === typeof value) { 763 | value = this.config.showSaveCard; 764 | } else { 765 | this.config.showSaveCard = (true === value); 766 | } 767 | this._sendEvent('savecard', { show: value }); 768 | } 769 | 770 | 771 | XPaymentsWidget.prototype.refresh = function() 772 | { 773 | this._sendEvent('refresh'); 774 | } 775 | 776 | XPaymentsWidget.prototype.resize = function(height) 777 | { 778 | var elm = this.getIframeElm(); 779 | if (elm) { 780 | this.previousHeight = elm.style.height; 781 | elm.style.height = height + 'px'; 782 | } 783 | } 784 | 785 | XPaymentsWidget.prototype.setOrder = function(total, currency) 786 | { 787 | if ('undefined' !== typeof total) { 788 | this.config.order.total = total; 789 | } 790 | if ('undefined' !== typeof currency) { 791 | this.config.order.currency = currency; 792 | } 793 | 794 | this._sendEvent('details', { 795 | tokenizeCard: this.config.order.tokenizeCard, 796 | total: this.config.order.total, 797 | currency: this.config.order.currency 798 | }); 799 | } 800 | 801 | XPaymentsWidget.prototype.destroy = function() 802 | { 803 | if (this.bindedListener) { 804 | window.removeEventListener('message', this.bindedListener); 805 | } 806 | 807 | var formElm = this.getFormElm(); 808 | if (this.bindedSubmit && formElm) { 809 | formElm.removeEventListener('submit', this.bindedSubmit); 810 | } 811 | 812 | var containerElm = this.getContainerElm(); 813 | if (containerElm) { 814 | var elm = this.getIframeElm(); 815 | if (elm && containerElm.contains(elm)) { 816 | containerElm.removeChild(elm); 817 | } 818 | } 819 | } 820 | 821 | XPaymentsWidget.prototype.messageListener = function(event) 822 | { 823 | if (window.JSON) { 824 | var msg = false; 825 | if (-1 !== this.getServerUrl().toLowerCase().indexOf(event.origin.toLowerCase())) { 826 | try { 827 | msg = window.JSON.parse(event.data); 828 | } catch (e) { 829 | // Skip invalid messages 830 | } 831 | } 832 | 833 | if ( 834 | msg && 835 | msg.event && 836 | 0 === msg.event.indexOf(this.messageNamespace) && 837 | (!msg.widgetId || msg.widgetId === this.widgetId) 838 | ) { 839 | this._log('Received from X-Payments: ' + msg.event, msg.params); 840 | 841 | var eventType = msg.event.substr(this.messageNamespace.length); 842 | 843 | if ('loaded' === eventType) { 844 | this._afterLoad(msg.params); 845 | } else if ('applepay.start' === eventType) { 846 | this._applePayStart(msg.params); 847 | } else if ('applepay.init' === eventType) { 848 | this._applePayInit(msg.params); 849 | } else if ('applepay.merchantvalidated' === eventType) { 850 | this._applePayValidated(msg.params); 851 | } else if ('applepay.completed' === eventType) { 852 | this._applePayCompleted(msg.params); 853 | } else if ('applepay.error' === eventType) { 854 | this._applePayError(msg.params); 855 | } else if ('googlepay.init' === eventType) { 856 | this._googlePayInit(msg.params); 857 | } else if ('googlepay.start' === eventType) { 858 | this._googlePayStart(msg.params); 859 | } else if ('googlepay.error' === eventType) { 860 | this._googlePayError(msg.params); 861 | } else if ('paymentmethod.change' === eventType) { 862 | this._paymentMethodChange(msg.params); 863 | } else if ('resize' === eventType) { 864 | this.resize(msg.params.height); 865 | } else if ('alert' === eventType) { 866 | msg.params.message = 867 | ('string' === typeof msg.params.message) 868 | ? msg.params.message.replace(/<\/?[^>]+>/gi, '') 869 | : ''; 870 | } 871 | 872 | this.trigger(eventType, msg.params); 873 | } 874 | 875 | } 876 | } 877 | 878 | XPaymentsWidget.prototype._isDebugMode = function() 879 | { 880 | return this.config.debug; 881 | } 882 | 883 | XPaymentsWidget.prototype._log = function(msg, params) 884 | { 885 | if (this._isDebugMode()) { 886 | console.groupCollapsed(msg); 887 | if ('undefined' !== typeof params) { 888 | console.log(JSON.stringify(params)); 889 | } 890 | console.trace(); 891 | console.groupEnd(); 892 | } 893 | } 894 | 895 | XPaymentsWidget.prototype._sendEvent = function(eventName, eventParams) 896 | { 897 | if ('undefined' === typeof eventParams) { 898 | eventParams = {}; 899 | } 900 | 901 | this._postMessage({ 902 | event: this.receiverNamespace + eventName, 903 | params: eventParams 904 | }) 905 | } 906 | 907 | XPaymentsWidget.prototype._postMessage = function(message) 908 | { 909 | var elm = this.getIframeElm(); 910 | if ( 911 | window.postMessage 912 | && window.JSON 913 | && elm 914 | && elm.contentWindow 915 | ) { 916 | this._log('Sent to X-Payments: ' + message.event, message.params); 917 | elm.contentWindow.postMessage(window.JSON.stringify(message), '*'); 918 | } else { 919 | this._log('Error sending message - iframe wasn\'t initialized!'); 920 | } 921 | } 922 | -------------------------------------------------------------------------------- /lib/XPaymentsCloud/ApiException.php: -------------------------------------------------------------------------------- 1 | publicMessage; 23 | } 24 | 25 | /** 26 | * Set error message that can be displayed to customer 27 | * 28 | * @param $message 29 | * 30 | * @return void 31 | */ 32 | public function setPublicMessage($message) 33 | { 34 | $this->publicMessage = $message; 35 | } 36 | } -------------------------------------------------------------------------------- /lib/XPaymentsCloud/Client.php: -------------------------------------------------------------------------------- 1 | account, $this->apiKey, $this->secretKey); 48 | 49 | $params = array( 50 | 'token' => $token, 51 | 'refId' => $refId, 52 | 'customerId' => $customerId, 53 | 'cart' => $cart, 54 | 'returnUrl' => $returnUrl, 55 | 'callbackUrl' => $callbackUrl, 56 | ); 57 | 58 | if (!is_null($forceSaveCard)) { 59 | $params['forceSaveCard'] = ($forceSaveCard) ? 'Y' : 'N'; 60 | } 61 | if (!is_null($forceTransactionType)) { 62 | $params['forceTransactionType'] = $forceTransactionType; 63 | } 64 | if ($forceConfId) { 65 | $params['confId'] = $forceConfId; 66 | } 67 | 68 | $response = $request->send( 69 | 'pay', 70 | $params 71 | ); 72 | 73 | if (empty($response->getPayment())) { 74 | throw new ApiException('Invalid response'); 75 | } 76 | 77 | return $response; 78 | } 79 | 80 | /** 81 | * Call "tokenize_card" action 82 | * 83 | * @param string $token 84 | * @param string $refId 85 | * @param string $customerId 86 | * @param array $cart 87 | * @param string $returnUrl 88 | * @param string $callbackUrl 89 | * @param int $forceConfId (optional) 90 | * 91 | * @return Response 92 | * @throws ApiException 93 | */ 94 | public function doTokenizeCard($token, $refId, $customerId, $cart, $returnUrl, $callbackUrl, $forceConfId = 0) 95 | { 96 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 97 | 98 | $params = array( 99 | 'token' => $token, 100 | 'refId' => $refId, 101 | 'customerId' => $customerId, 102 | 'cart' => $cart, 103 | 'returnUrl' => $returnUrl, 104 | 'callbackUrl' => $callbackUrl, 105 | ); 106 | 107 | if ($forceConfId) { 108 | $params['confId'] = $forceConfId; 109 | } 110 | 111 | $response = $request->send( 112 | 'tokenize_card', 113 | $params 114 | ); 115 | 116 | if (empty($response->getPayment())) { 117 | throw new ApiException('Invalid response'); 118 | } 119 | 120 | return $response; 121 | } 122 | 123 | /** 124 | * @param $xpid 125 | * @param int $amount 126 | * @return Response 127 | * @throws ApiException 128 | */ 129 | public function doCapture($xpid, $amount = 0) 130 | { 131 | return $this->doAction('capture', $xpid, $amount); 132 | } 133 | 134 | /** 135 | * @param $xpid 136 | * @param int $amount 137 | * @return Response 138 | * @throws ApiException 139 | */ 140 | public function doRefund($xpid, $amount = 0) 141 | { 142 | return $this->doAction('refund', $xpid, $amount); 143 | } 144 | 145 | /** 146 | * @param $xpid 147 | * @param int $amount 148 | * @return Response 149 | * @throws ApiException 150 | */ 151 | public function doVoid($xpid, $amount = 0) 152 | { 153 | return $this->doAction('void', $xpid, $amount); 154 | } 155 | 156 | /** 157 | * @param $xpid 158 | * @return Response 159 | * @throws ApiException 160 | */ 161 | public function doGetInfo($xpid) 162 | { 163 | return $this->doAction('get_info', $xpid); 164 | } 165 | 166 | /** 167 | * @param $xpid 168 | * @return Response 169 | * @throws ApiException 170 | */ 171 | public function doContinue($xpid) 172 | { 173 | return $this->doAction('continue', $xpid); 174 | } 175 | 176 | /** 177 | * @param $xpid 178 | * @return Response 179 | * @throws ApiException 180 | */ 181 | public function doAccept($xpid) 182 | { 183 | return $this->doAction('accept', $xpid); 184 | } 185 | 186 | /** 187 | * @param $xpid 188 | * @return Response 189 | * @throws ApiException 190 | */ 191 | public function doDecline($xpid) 192 | { 193 | return $this->doAction('decline', $xpid); 194 | } 195 | 196 | /** 197 | * @param string $refId 198 | * @param string $customerId 199 | * @param string $callbackUrl 200 | * @param string $xpid 201 | * @param array $cart 202 | * @return Response 203 | * @throws ApiException 204 | */ 205 | public function doRebill(string $refId, string $customerId, string $callbackUrl, string $xpid, array $cart) 206 | { 207 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 208 | 209 | $params = [ 210 | 'refId' => $refId, 211 | 'customerId' => $customerId, 212 | 'callbackUrl' => $callbackUrl, 213 | 'xpid' => $xpid, 214 | 'cart' => $cart, 215 | ]; 216 | 217 | $response = $request->send('rebill', $params); 218 | 219 | if (is_null($response->getPayment())) { 220 | throw new ApiException('Invalid response'); 221 | } 222 | 223 | return $response; 224 | } 225 | 226 | /** 227 | * @param $action 228 | * @param $xpid 229 | * @param int $amount 230 | * @return Response 231 | * @throws ApiException 232 | */ 233 | private function doAction($action, $xpid, $amount = 0) 234 | { 235 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 236 | 237 | $params = array( 238 | 'xpid' => $xpid, 239 | ); 240 | 241 | if (0 < $amount) { 242 | $params['amount'] = $amount; 243 | } 244 | 245 | $response = $request->send( 246 | $action, 247 | $params 248 | ); 249 | 250 | if (is_null($response->result)) { 251 | throw new ApiException('Invalid response'); 252 | } 253 | 254 | return $response; 255 | } 256 | 257 | /** 258 | * Get all customer's valid cards. Note: this doesn't include expired cards 259 | * and cards saved by switched off payment configuration 260 | * 261 | * @param string $customerId Public Customer ID 262 | * @param string $status Cards status 263 | * 264 | * @return Response 265 | * 266 | * @throws ApiException 267 | */ 268 | public function doGetCustomerCards($customerId, $status = 'any') 269 | { 270 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 271 | 272 | $params = array( 273 | 'customerId' => $customerId, 274 | 'status' => $status, 275 | ); 276 | 277 | $response = $request->send( 278 | 'get_cards', 279 | $params, 280 | 'customer' 281 | ); 282 | 283 | if (is_null($response->cards)) { 284 | throw new ApiException('Invalid response'); 285 | } 286 | 287 | return $response; 288 | } 289 | 290 | /** 291 | * Checks if card tokenization is possible (for any or particular customer) and other settings 292 | * 293 | * @param string $customerId Public Customer ID (optional) 294 | * 295 | * @return Response 296 | * 297 | * @throws ApiException 298 | */ 299 | public function doGetTokenizationSettings($customerId = '') 300 | { 301 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 302 | 303 | $params = array( 304 | 'customerId' => $customerId, 305 | ); 306 | 307 | $response = $request->send( 308 | 'get_tokenization_settings', 309 | $params, 310 | 'config' 311 | ); 312 | 313 | if (is_null($response->tokenizationEnabled)) { 314 | throw new ApiException('Invalid response'); 315 | } 316 | 317 | return $response; 318 | } 319 | 320 | /** 321 | * Set default customer's card 322 | * 323 | * @param string $customerId Public Customer ID 324 | * @param string $cardId Card ID 325 | * 326 | * @return Response 327 | * 328 | * @throws ApiException 329 | */ 330 | public function doSetDefaultCustomerCard($customerId, $cardId) 331 | { 332 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 333 | 334 | $params = array( 335 | 'customerId' => $customerId, 336 | 'cardId' => $cardId, 337 | ); 338 | 339 | $response = $request->send( 340 | 'set_default_card', 341 | $params, 342 | 'customer' 343 | ); 344 | 345 | if (is_null($response->result)) { 346 | throw new ApiException('Invalid response'); 347 | } 348 | 349 | return $response; 350 | } 351 | 352 | /** 353 | * Delete customer card 354 | * 355 | * @param string $customerId Public Customer ID 356 | * @param string $cardId Card ID 357 | * 358 | * @return Response 359 | * 360 | * @throws ApiException 361 | */ 362 | public function doDeleteCustomerCard($customerId, $cardId) 363 | { 364 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 365 | 366 | $params = array( 367 | 'customerId' => $customerId, 368 | 'cardId' => $cardId, 369 | ); 370 | 371 | $response = $request->send( 372 | 'delete_card', 373 | $params, 374 | 'customer' 375 | ); 376 | 377 | if (is_null($response->result)) { 378 | throw new ApiException('Invalid response'); 379 | } 380 | 381 | return $response; 382 | } 383 | 384 | /** 385 | * Get payment configurations 386 | * 387 | * @return Response 388 | * @throws ApiException 389 | */ 390 | public function doGetPaymentConfs() 391 | { 392 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 393 | 394 | $params = array(); 395 | 396 | $response = $request->send( 397 | 'get_payment_configurations', 398 | $params, 399 | 'config' 400 | ); 401 | 402 | if (is_null($response->paymentModule)) { 403 | throw new ApiException('Invalid response'); 404 | } 405 | 406 | return $response; 407 | } 408 | 409 | /** 410 | * Get wallets 411 | * 412 | * @return Response 413 | * @throws ApiException 414 | */ 415 | public function doGetWallets() 416 | { 417 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 418 | 419 | $params = array(); 420 | 421 | $response = $request->send( 422 | 'get_wallets', 423 | $params, 424 | 'config' 425 | ); 426 | 427 | if (is_null($response->result)) { 428 | throw new ApiException('Invalid response'); 429 | } 430 | 431 | return $response; 432 | } 433 | 434 | /** 435 | * Change wallet status 436 | * 437 | * @param string $walletId Wallet public ID (same as in JS) 438 | * @param bool $status Send True to enable, false to disable 439 | * 440 | * @return Response 441 | * @throws ApiException 442 | */ 443 | public function doSetWalletStatus($walletId, $status) 444 | { 445 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 446 | 447 | $params = array( 448 | 'walletId' => $walletId, 449 | 'status' => $status 450 | ); 451 | 452 | $response = $request->send( 453 | 'set_wallet_status', 454 | $params, 455 | 'config' 456 | ); 457 | 458 | if (is_null($response->result)) { 459 | throw new ApiException('Invalid response'); 460 | } 461 | 462 | return $response; 463 | } 464 | 465 | /** 466 | * Verify Apple Pay domain 467 | * 468 | * @param bool $status 469 | * 470 | * @return Response 471 | * @throws ApiException 472 | */ 473 | public function doVerifyApplePayDomain($domain) 474 | { 475 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 476 | 477 | $params = array( 478 | 'domain' => $domain 479 | ); 480 | 481 | $response = $request->send( 482 | 'verify_apple_pay_domain', 483 | $params, 484 | 'config' 485 | ); 486 | 487 | if (is_null($response->result)) { 488 | throw new ApiException('Invalid response'); 489 | } 490 | 491 | return $response; 492 | } 493 | 494 | /** 495 | * Update subscription 496 | * 497 | * @param string $subscriptionPublicId 498 | * @param array $updateParams 499 | * 500 | * @return Response 501 | * 502 | * @throws ApiException 503 | */ 504 | public function doUpdateSubscription(string $subscriptionPublicId, array $updateParams) 505 | { 506 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 507 | 508 | $params = ['public_id' => $subscriptionPublicId] + $updateParams; 509 | 510 | $response = $request->send( 511 | 'update_subscription', 512 | $params, 513 | 'subscription' 514 | ); 515 | 516 | if (is_null($response->result)) { 517 | throw new ApiException('Invalid response'); 518 | } 519 | 520 | return $response; 521 | } 522 | 523 | /** 524 | * Get subscriptions settings 525 | * 526 | * @return Response 527 | * 528 | * @throws ApiException 529 | */ 530 | public function doGetSubscriptionsSettings() 531 | { 532 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 533 | 534 | $params = []; 535 | 536 | $response = $request->send( 537 | 'get_settings', 538 | $params, 539 | 'subscription' 540 | ); 541 | 542 | if (is_null($response)) { 543 | throw new ApiException('Invalid response'); 544 | } 545 | 546 | return $response; 547 | } 548 | 549 | /** 550 | * Create subscriptions using saved card id 551 | * 552 | * @param array $subscriptionPlans 553 | * @param string $customerId 554 | * @param string $xpid 555 | * @param string $savedCardId 556 | * 557 | * @return Response 558 | * 559 | * @throws ApiException 560 | */ 561 | public function doCreateSubscriptions(array $subscriptionPlans, string $customerId, string $xpid, string $savedCardId = ''): Response 562 | { 563 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 564 | 565 | $params = [ 566 | 'subscriptionPlans' => $subscriptionPlans, 567 | 'customerId' => $customerId, 568 | 'xpid' => $xpid, 569 | 'savedCardId' => $savedCardId, 570 | ]; 571 | 572 | $response = $request->send( 573 | 'create_subscriptions', 574 | $params, 575 | 'subscription' 576 | ); 577 | 578 | if (is_null($response->getSubscriptions())) { 579 | throw new ApiException('Invalid response'); 580 | } 581 | 582 | return $response; 583 | } 584 | 585 | /** 586 | * Add bulk operation 587 | * 588 | * @param string $operation 589 | * @param array $xpids 590 | * 591 | * @return Response 592 | */ 593 | public function doAddBulkOperation($operation, array $xpids = array()) 594 | { 595 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 596 | 597 | $params = array( 598 | 'operation' => $operation, 599 | 'payments' => array(), 600 | ); 601 | 602 | foreach ($xpids as $xpid) { 603 | $params['payments'][] = array( 604 | 'xpid' => $xpid 605 | ); 606 | } 607 | 608 | $response = $request->send( 609 | 'add', 610 | $params, 611 | 'bulk_operation' 612 | ); 613 | 614 | return $response; 615 | } 616 | 617 | /** 618 | * Delete bulk operation 619 | * 620 | * @param string $batchId 621 | * 622 | * @return Response 623 | */ 624 | public function doDeleteBulkOperation($batchId) 625 | { 626 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 627 | 628 | $params = array( 629 | 'batch_id' => $batchId, 630 | ); 631 | 632 | $response = $request->send( 633 | 'delete', 634 | $params, 635 | 'bulk_operation' 636 | ); 637 | 638 | return $response; 639 | } 640 | 641 | /** 642 | * Get bulk operation 643 | * 644 | * @param string $batchId 645 | * 646 | * @return Response 647 | */ 648 | public function doGetBulkOperation($batchId) 649 | { 650 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 651 | 652 | $params = array( 653 | 'batch_id' => $batchId, 654 | ); 655 | 656 | $response = $request->send( 657 | 'get', 658 | $params, 659 | 'bulk_operation' 660 | ); 661 | 662 | return $response; 663 | } 664 | 665 | /** 666 | * Start bulk operation 667 | * 668 | * @param string $batchId 669 | * 670 | * @return Response 671 | */ 672 | public function doStartBulkOperation($batchId) 673 | { 674 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 675 | 676 | $params = array( 677 | 'batch_id' => $batchId, 678 | ); 679 | 680 | $response = $request->send( 681 | 'start', 682 | $params, 683 | 'bulk_operation' 684 | ); 685 | 686 | return $response; 687 | } 688 | 689 | /** 690 | * Stop bulk operation 691 | * 692 | * @param string $batchId 693 | * 694 | * @return Response 695 | */ 696 | public function doStopBulkOperation($batchId) 697 | { 698 | $request = new Request($this->account, $this->apiKey, $this->secretKey); 699 | 700 | $params = array( 701 | 'batch_id' => $batchId, 702 | ); 703 | 704 | $response = $request->send( 705 | 'stop', 706 | $params, 707 | 'bulk_operation' 708 | ); 709 | 710 | return $response; 711 | } 712 | 713 | /** 714 | * @param null $inputData 715 | * @param null $signature 716 | * 717 | * @return Response 718 | * 719 | * @throws ApiException 720 | */ 721 | public function parseCallback($inputData = null, $signature = null) 722 | { 723 | if (is_null($inputData)) { 724 | $inputData = file_get_contents('php://input'); 725 | } 726 | if (is_null($signature) && !empty($_SERVER)) { 727 | $header = 'HTTP_' . strtoupper(str_replace('-', '_', Signature::HEADER)); 728 | $signature = (array_key_exists($header, $_SERVER)) ? $_SERVER[$header] : ''; 729 | } 730 | 731 | $response = new Response('callback', $inputData, null, $signature, $this->secretKey); 732 | 733 | if ( 734 | empty($response->getPayment()) 735 | && !$response->getSubscription() 736 | ) { 737 | throw new ApiException('Invalid response'); 738 | } 739 | 740 | return $response; 741 | } 742 | 743 | /** 744 | * Get X-Payments web location 745 | * 746 | * @return string 747 | */ 748 | public function getXpaymentsWebLocation() 749 | { 750 | $host = $this->account . '.' . Request::XP_DOMAIN; 751 | 752 | if (defined('XPAYMENTS_SDK_DEBUG_SERVER_HOST')) { 753 | $host = constant('XPAYMENTS_SDK_DEBUG_SERVER_HOST'); 754 | } 755 | 756 | return sprintf('https://%s/', $host); 757 | } 758 | 759 | /** 760 | * Get X-Payments admin URL 761 | * 762 | * @return string 763 | */ 764 | public function getAdminUrl() 765 | { 766 | return $this->getXpaymentsWebLocation() . 'admin.php'; 767 | } 768 | 769 | /** 770 | * Get X-Payments payment URL 771 | * 772 | * @return string 773 | */ 774 | public function getPaymentUrl() 775 | { 776 | return $this->getXpaymentsWebLocation() . 'payment.php'; 777 | } 778 | 779 | /** 780 | * Client constructor. 781 | * @param string $account 782 | * @param string $apiKey 783 | * @param string $secretKey 784 | */ 785 | public function __construct($account, $apiKey, $secretKey) 786 | { 787 | $this->account = $account; 788 | $this->apiKey = $apiKey; 789 | $this->secretKey = $secretKey; 790 | } 791 | 792 | } 793 | -------------------------------------------------------------------------------- /lib/XPaymentsCloud/Model/Payment.php: -------------------------------------------------------------------------------- 1 | createFromResponse($paymentData); 82 | } 83 | } 84 | 85 | public function createFromResponse($paymentData) 86 | { 87 | // TODO: validate fields 88 | 89 | foreach ($paymentData as $key => $field) { 90 | if (is_array($field) && 'supportedTransactions' != $key) { 91 | $paymentData[$key] = (object)$field; 92 | } 93 | if (property_exists($this, $key)) { 94 | $this->{$key} = $paymentData[$key]; 95 | } 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * @param string $txnCode 103 | * 104 | * @return bool 105 | */ 106 | public function isTransactionSupported($txnCode) 107 | { 108 | return in_array($txnCode, $this->supportedTransactions); 109 | } 110 | 111 | /** 112 | * Returns true if payment is not failed 113 | * 114 | * @return bool 115 | */ 116 | public function isSuccessfulStatus() 117 | { 118 | return !in_array($this->status, [self::INITIALIZED, self::DECLINED]); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/XPaymentsCloud/Model/Subscription.php: -------------------------------------------------------------------------------- 1 | publicId; 77 | } 78 | 79 | /** 80 | * @param mixed $publicId 81 | * 82 | * @return Subscription 83 | */ 84 | public function setPublicId($publicId) 85 | { 86 | $this->publicId = $publicId; 87 | return $this; 88 | } 89 | 90 | /** 91 | * @return mixed 92 | */ 93 | public function getType() 94 | { 95 | return $this->type; 96 | } 97 | 98 | /** 99 | * @param mixed $type 100 | * 101 | * @return Subscription 102 | */ 103 | public function setType($type) 104 | { 105 | $this->type = $type; 106 | return $this; 107 | } 108 | 109 | /** 110 | * @return mixed 111 | */ 112 | public function getNumber() 113 | { 114 | return $this->number; 115 | } 116 | 117 | /** 118 | * @param mixed $number 119 | * 120 | * @return Subscription 121 | */ 122 | public function setNumber($number) 123 | { 124 | $this->number = $number; 125 | return $this; 126 | } 127 | 128 | /** 129 | * @return mixed 130 | */ 131 | public function getPeriod() 132 | { 133 | return $this->period; 134 | } 135 | 136 | /** 137 | * @param mixed $period 138 | * 139 | * @return Subscription 140 | */ 141 | public function setPeriod($period) 142 | { 143 | $this->period = $period; 144 | return $this; 145 | } 146 | 147 | /** 148 | * @return mixed 149 | */ 150 | public function getReverse() 151 | { 152 | return $this->reverse; 153 | } 154 | 155 | /** 156 | * @param mixed $reverse 157 | * 158 | * @return Subscription 159 | */ 160 | public function setReverse($reverse) 161 | { 162 | $this->reverse = $reverse; 163 | return $this; 164 | } 165 | 166 | /** 167 | * @return mixed 168 | */ 169 | public function getPeriods() 170 | { 171 | return $this->periods; 172 | } 173 | 174 | /** 175 | * @param mixed $periods 176 | * 177 | * @return Subscription 178 | */ 179 | public function setPeriods($periods) 180 | { 181 | $this->periods = $periods; 182 | return $this; 183 | } 184 | 185 | /** 186 | * @return mixed 187 | */ 188 | public function getRecurringAmount() 189 | { 190 | return $this->recurringAmount; 191 | } 192 | 193 | /** 194 | * @param mixed $recurringAmount 195 | * 196 | * @return Subscription 197 | */ 198 | public function setRecurringAmount($recurringAmount) 199 | { 200 | $this->recurringAmount = $recurringAmount; 201 | return $this; 202 | } 203 | 204 | /** 205 | * @return mixed 206 | */ 207 | public function getCardId() 208 | { 209 | return $this->cardId; 210 | } 211 | 212 | /** 213 | * @param mixed $cardId 214 | * 215 | * @return Subscription 216 | */ 217 | public function setCardId($cardId) 218 | { 219 | $this->cardId = $cardId; 220 | return $this; 221 | } 222 | 223 | /** 224 | * @return mixed 225 | */ 226 | public function getFailedAttempts() 227 | { 228 | return $this->failedAttempts; 229 | } 230 | 231 | /** 232 | * @param mixed $failedAttempts 233 | * 234 | * @return Subscription 235 | */ 236 | public function setFailedAttempts($failedAttempts) 237 | { 238 | $this->failedAttempts = $failedAttempts; 239 | return $this; 240 | } 241 | 242 | /** 243 | * @return mixed 244 | */ 245 | public function getSuccessfulAttempts() 246 | { 247 | return $this->successfulAttempts; 248 | } 249 | 250 | /** 251 | * @param mixed $successfulAttempts 252 | * 253 | * @return Subscription 254 | */ 255 | public function setSuccessfulAttempts($successfulAttempts) 256 | { 257 | $this->successfulAttempts = $successfulAttempts; 258 | return $this; 259 | } 260 | 261 | /** 262 | * @return mixed 263 | */ 264 | public function getStartDate() 265 | { 266 | return $this->startDate; 267 | } 268 | 269 | /** 270 | * @param mixed $startDate 271 | * 272 | * @return Subscription 273 | */ 274 | public function setStartDate($startDate) 275 | { 276 | $this->startDate = $startDate; 277 | return $this; 278 | } 279 | 280 | /** 281 | * @return mixed 282 | */ 283 | public function getPlannedDate() 284 | { 285 | return $this->plannedDate; 286 | } 287 | 288 | /** 289 | * @param mixed $plannedDate 290 | * 291 | * @return Subscription 292 | */ 293 | public function setPlannedDate($plannedDate) 294 | { 295 | $this->plannedDate = $plannedDate; 296 | return $this; 297 | } 298 | 299 | /** 300 | * @return mixed 301 | */ 302 | public function getActualDate() 303 | { 304 | return $this->actualDate; 305 | } 306 | 307 | /** 308 | * @param mixed $actualDate 309 | * 310 | * @return Subscription 311 | */ 312 | public function setActualDate($actualDate) 313 | { 314 | $this->actualDate = $actualDate; 315 | return $this; 316 | } 317 | 318 | /** 319 | * @return mixed 320 | */ 321 | public function getStatus() 322 | { 323 | return $this->status; 324 | } 325 | 326 | /** 327 | * @param mixed $status 328 | * 329 | * @return Subscription 330 | */ 331 | public function setStatus($status) 332 | { 333 | $this->status = $status; 334 | return $this; 335 | } 336 | 337 | /** 338 | * @return mixed 339 | */ 340 | public function getUniqueOrderItemId() 341 | { 342 | return $this->uniqueOrderItemId; 343 | } 344 | 345 | /** 346 | * @param mixed $uniqueOrderItemId 347 | */ 348 | public function setUniqueOrderItemId($uniqueOrderItemId): void 349 | { 350 | $this->uniqueOrderItemId = $uniqueOrderItemId; 351 | } 352 | 353 | /** 354 | * Subscription constructor. 355 | * 356 | * @param array $subscriptionData 357 | */ 358 | public function __construct(array $subscriptionData = []) 359 | { 360 | if ($subscriptionData) { 361 | $this->createFromResponse($subscriptionData); 362 | } 363 | } 364 | 365 | /** 366 | * @param $subscriptionData 367 | * 368 | * @return $this 369 | */ 370 | private function createFromResponse($subscriptionData) 371 | { 372 | // TODO: validate fields 373 | 374 | foreach ($subscriptionData as $key => $field) { 375 | 376 | if (property_exists($this, $key)) { 377 | $this->{$key} = $subscriptionData[$key]; 378 | } 379 | } 380 | 381 | return $this; 382 | } 383 | 384 | /** 385 | * @return bool 386 | */ 387 | public function isActive() 388 | { 389 | return self::STATUS_ACTIVE === $this->getStatus(); 390 | } 391 | 392 | } 393 | -------------------------------------------------------------------------------- /lib/XPaymentsCloud/Request.php: -------------------------------------------------------------------------------- 1 | account = $account; 33 | $this->apiKey = $apiKey; 34 | $this->secretKey = $secretKey; 35 | } 36 | 37 | /** 38 | * Send API request 39 | * 40 | * @param string $action 41 | * @param array $requestData 42 | * @param string $controller 43 | * 44 | * @return Response 45 | * @throws ApiException 46 | */ 47 | public function send($action, $requestData, $controller = 'payment') 48 | { 49 | // Prepare date 50 | $url = $this->getApiEndpoint($action, $controller); 51 | $post = $this->convertHashToJSON($requestData); 52 | $signature = Signature::get($action, $post, $this->secretKey); 53 | $postHeaders = array( 54 | 'Authorization: Basic ' . base64_encode($this->account . ':' . $this->apiKey), 55 | 'Content-Type: application/json', 56 | Signature::HEADER . ': ' . $signature, 57 | ); 58 | 59 | // Send data and get response 60 | $ch = $this->initCURL($url, $post, $postHeaders); 61 | $body = curl_exec($ch); 62 | $error = curl_error($ch); 63 | $errNo = curl_errno($ch); 64 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 65 | $signatureFromHeaders = $this->getSignatureFromHeaders(); 66 | 67 | if ( 68 | !empty($error) 69 | || 0 != $errNo 70 | ) { 71 | throw new ApiException($error ?: 'Communication error', $errNo); 72 | } 73 | 74 | $response = new Response($action, $body, $httpCode, $signatureFromHeaders, $this->secretKey); 75 | 76 | return $response; 77 | } 78 | 79 | /** 80 | * Returns X-Payments server host 81 | * 82 | * @return string 83 | */ 84 | private function getServerHost() 85 | { 86 | $host = $this->account . '.' . static::XP_DOMAIN; 87 | 88 | if (defined('XPAYMENTS_SDK_DEBUG_SERVER_HOST')) { 89 | $host = constant('XPAYMENTS_SDK_DEBUG_SERVER_HOST'); 90 | } 91 | 92 | return $host; 93 | } 94 | 95 | /** 96 | * @param $action API action 97 | * @param $controller API controller 98 | * 99 | * @return string 100 | */ 101 | private function getApiEndpoint($action, $controller) 102 | { 103 | return 'https://' . $this->getServerHost() . '/api/' . static::API_VERSION . '/' . $controller . '/' . $action; 104 | } 105 | 106 | /** 107 | * Initializes cURL resource for posting data 108 | * 109 | * @param string $url URL to which send data 110 | * @param string $content Data to post 111 | * @param string $headers Headers for content 112 | * 113 | * @return resource 114 | */ 115 | private function initCURL($url, $content, $headers) 116 | { 117 | // Clear static var 118 | $this->getSignatureFromHeaders(); 119 | 120 | $ch = curl_init(); 121 | 122 | curl_setopt($ch, CURLOPT_URL, $url); 123 | curl_setopt($ch, CURLOPT_HEADER, false); 124 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->connectionTimeout); 125 | curl_setopt($ch, CURLOPT_POST, true); 126 | curl_setopt($ch, CURLOPT_MAXREDIRS, 10); 127 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); 128 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 129 | curl_setopt($ch, CURLOPT_POSTFIELDS, $content); 130 | 131 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 132 | curl_setopt($ch, CURLOPT_ENCODING, ''); 133 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 134 | 135 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'getSignatureFromHeaders')); 136 | 137 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 138 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); 139 | 140 | return $ch; 141 | } 142 | 143 | /** 144 | * Convert hash array to JSON 145 | * 146 | * @param array $data Hash array 147 | * 148 | * @return string 149 | */ 150 | private function convertHashToJSON(array $data) 151 | { 152 | return json_encode($data); 153 | } 154 | 155 | /** 156 | * CURL headers collector callback used to get signature header 157 | * 158 | * @return mixed 159 | */ 160 | protected function getSignatureFromHeaders() 161 | { 162 | static $signature = ''; 163 | 164 | $args = func_get_args(); 165 | 166 | if (0 == count($args)) { 167 | $return = $signature; 168 | $signature = ''; 169 | } else { 170 | if (0 === stripos($args[1], Signature::HEADER)) { 171 | $signature = substr(trim($args[1]), strlen(Signature::HEADER) + 2); 172 | } 173 | $return = strlen($args[1]); 174 | } 175 | 176 | return $return; 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /lib/XPaymentsCloud/Response.php: -------------------------------------------------------------------------------- 1 | convertJSONToHash($body); 53 | 54 | if (500 === $httpCode) { 55 | throw new ApiException('Server returned error: ' . $body, $httpCode); 56 | } 57 | 58 | if ( 59 | 0 !== strcmp($signatureLocal, $signature) 60 | || empty($fields) 61 | || !is_array($fields) 62 | ) { 63 | throw new ApiException('Invalid response signature', 403); 64 | } 65 | 66 | if ( 67 | !is_null($httpCode) 68 | && 200 !== $httpCode 69 | ) { 70 | // API error returned 71 | $errNo = !empty($fields['code']) ? $fields['code'] : $httpCode; 72 | $error = !empty($fields['error']) ? $fields['error'] : 'Request could not be completed'; 73 | $exception = new ApiException($error, $errNo); 74 | if (!empty($fields['message'])) { 75 | $exception->setPublicMessage($fields['message']); 76 | } 77 | throw $exception; 78 | } 79 | 80 | if (!empty($fields['payment'])) { 81 | $this->payment = new \XPaymentsCloud\Model\Payment($fields['payment']); 82 | unset($fields['payment']); 83 | } 84 | 85 | if (!empty($fields['subscription'])) { 86 | $this->subscription = new Subscription($fields['subscription']); 87 | unset($fields['subscription']); 88 | } 89 | 90 | if ( 91 | !empty($fields['subscriptions']) 92 | && is_array($fields['subscriptions']) 93 | ) { 94 | foreach ($fields['subscriptions'] as $subscription) { 95 | $this->subscriptions[] = new Subscription($subscription); 96 | } 97 | unset($fields['subscriptions']); 98 | } 99 | 100 | $this->fields = $fields; 101 | } 102 | 103 | /** 104 | * Convert JSON to hash array 105 | * 106 | * @param string $json JSON string 107 | * 108 | * @return array|string 109 | */ 110 | private function convertJSONToHash($json) 111 | { 112 | return json_decode($json, true); 113 | } 114 | 115 | /** 116 | * @param $param 117 | * @return mixed 118 | */ 119 | public function __get($param) 120 | { 121 | return (array_key_exists($param, $this->fields)) ? $this->fields[$param] : null; 122 | } 123 | 124 | /** 125 | * @return Model\Payment 126 | */ 127 | public function getPayment() 128 | { 129 | return $this->payment; 130 | } 131 | 132 | /** 133 | * Check if last transaction was successful 134 | * 135 | * @return bool 136 | */ 137 | public function isLastTransactionSuccessful() 138 | { 139 | return !is_null($this->result) 140 | && in_array( 141 | $this->result, 142 | array( 143 | self::SUCCESS_STATUS, 144 | self::WARNING_STATUS, 145 | self::PENDING_STATUS, 146 | ) 147 | ); 148 | } 149 | 150 | /** 151 | * @return Subscription[] 152 | */ 153 | public function getSubscriptions() 154 | { 155 | return $this->subscriptions; 156 | } 157 | 158 | /** 159 | * @return Subscription 160 | */ 161 | public function getSubscription() 162 | { 163 | return $this->subscription; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /lib/XPaymentsCloud/Signature.php: -------------------------------------------------------------------------------- 1 |