You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

710 lines
29 KiB

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport"
  6. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8. <!--jquery为了调用服务端获取token-->
  9. <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  10. <script src="https://js.braintreegateway.com/web/3.71.0/js/client.js"></script>
  11. <script src="https://js.braintreegateway.com/web/3.71.0/js/hosted-fields.js"></script>
  12. <script src="https://js.braintreegateway.com/web/3.71.0/js/data-collector.min.js"></script>
  13. <!-- Latest compiled and minified CSS -->
  14. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  15. <!-- Optional theme -->
  16. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
  17. integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  18. <title>Payment</title>
  19. <style>
  20. body {
  21. background-color: #efecec;
  22. }
  23. button {
  24. width: 97px;
  25. height: 40px;
  26. padding: 10px;
  27. font-size: 16px;
  28. color: #fff;
  29. background-color: #47A2FF;
  30. border: none;
  31. border-radius: 5px;
  32. }
  33. .loading {
  34. margin: 20% auto;
  35. text-align: center;
  36. border: 16px solid #f3f3f3;
  37. border-radius: 50%;
  38. border-top: 16px solid #3498db;
  39. width: 120px;
  40. height: 120px;
  41. -webkit-animation: spin 2s linear infinite;
  42. animation: spin 2s linear infinite;
  43. z-index: 1000;
  44. display: none;
  45. }
  46. @-webkit-keyframes spin {
  47. 0% {
  48. -webkit-transform: rotate(0deg);
  49. }
  50. 100% {
  51. -webkit-transform: rotate(360deg);
  52. }
  53. }
  54. @keyframes spin {
  55. 0% {
  56. transform: rotate(0deg);
  57. }
  58. 100% {
  59. transform: rotate(360deg);
  60. }
  61. }
  62. .main {
  63. margin: 50px auto;
  64. width: 90%;
  65. max-width: 800px;
  66. background-color: #fff;
  67. padding: 10px 20px;
  68. min-height: 500px;
  69. border-radius: 10px;
  70. }
  71. /*.btn{width:100%;text-align: center;display: none;}*/
  72. /*hosted field*/
  73. body {
  74. background-color: #fff;
  75. padding: 15px;
  76. }
  77. .toast {
  78. position: fixed;
  79. top: 15px;
  80. right: 15px;
  81. z-index: 9999;
  82. }
  83. .bootstrap-basic {
  84. background: white;
  85. }
  86. /* Braintree Hosted Fields styling classes*/
  87. .braintree-hosted-fields-focused {
  88. color: #495057;
  89. background-color: #fff;
  90. border-color: #80bdff;
  91. outline: 0;
  92. box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
  93. }
  94. .braintree-hosted-fields-focused.is-invalid {
  95. border-color: #dc3545;
  96. box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
  97. }
  98. .row {
  99. margin-bottom: 40px;
  100. }
  101. .alert-fixed {
  102. position: fixed;
  103. top: 10px;
  104. left: 50%;
  105. width: 10%;
  106. z-index: 9999;
  107. border-radius: 0px;
  108. padding: 5px;
  109. }
  110. </style>
  111. </head>
  112. <body>
  113. <input type="hidden" name="_token" id="_token" value="{{.Authorization}}" />
  114. <input type="hidden" name="_submitUrl" id="_submitUrl" value="http://localhost:17764/api/homeapi/process" />
  115. <input type="hidden" name="_amt" id="_amt" value="{{.Amt}}" />
  116. <input type="hidden" name="_cur" id="_cur" value="{{.Currency}}" />
  117. <input type="hidden" name="_intent" id="_intent" value="{{.Intent}}" />
  118. <input type="hidden" name="_flow" id="_flow" value="{{.Flow}}" />
  119. <input type="hidden" name="_transactionNo" id="_transactionNo" value="{{.TransactionNo}}" />
  120. <input type="hidden" name="_vendor" id="_vendor" value="{{.Vendor}}" />
  121. <div class="alert-primary alert-fixed" style="text-align: center; display: none;" role="alert">
  122. <i class="fa fa-check-circle-o" aria-hidden="true" style="padding: 0 5px;"></i>Apply successfully!
  123. </div>
  124. <div class="loading" id="loading"></div>
  125. <div class="main" id="main">
  126. <div class="info">
  127. <p>Transaction No: {{.TransactionNo}}</p>
  128. <p>Amount: {{.Amt}}</p>
  129. <p>Currency: {{.Currency}}</p>
  130. </div>
  131. <div class="bootstrap-basic">
  132. <form class="needs-validation" novalidate="">
  133. <div class="row">
  134. <div class="form-item col-sm-12 mb-3 is-required">
  135. <label for="first-name">First Name</label>
  136. <input class="form-control" id="first-name">
  137. <!-- <small class="text-muted">First name as displayed on card</small>-->
  138. <div class="invalid-feedback">
  139. First Name is required
  140. </div>
  141. </div>
  142. <div class="form-item col-sm-12 mb-3 is-required">
  143. <label for="last-name">Last Name</label>
  144. <input class="form-control" id="last-name">
  145. <!-- <small class="text-muted">Last name as displayed on card</small>-->
  146. <div class="invalid-feedback">
  147. Last Name is required
  148. </div>
  149. </div>
  150. <div class="form-item col-sm-12 mb-3 is-required">
  151. <label for="email">Email</label>
  152. <input type="email" class="form-control" id="email" placeholder="[email protected]">
  153. <div class="invalid-feedback">
  154. Please enter a valid email
  155. </div>
  156. </div>
  157. <div class="form-item col-sm-12 mb-3 ">
  158. <label for="countryId">Country</label>
  159. <select class="countries form-control" id="countryId">
  160. <option value="">Select Country</option>
  161. </select>
  162. <div class="invalid-feedback">
  163. Please select country.
  164. </div>
  165. </div>
  166. <!--<div class="form-item col-sm-12 mb-3 is-required">-->
  167. <!--<label for="phone">Phone</label>-->
  168. <!--<input class="form-control" id="phone">-->
  169. <!--<div class="invalid-feedback">-->
  170. <!--Please enter phone.-->
  171. <!--</div>-->
  172. <!--</div>-->
  173. <div class="form-item col-sm-12 mb-3 ">
  174. <label for="stateId">State</label>
  175. <select class="states form-control" id="stateId">
  176. <option value="">Select State</option>
  177. </select>
  178. <div class="invalid-feedback">
  179. Please select state.
  180. </div>
  181. </div>
  182. <div class="form-item col-sm-12 mb-3 ">
  183. <label for="cityId">City</label>
  184. <select class="cities form-control" id="cityId">
  185. <option value="">Select City</option>
  186. </select>
  187. <div class="invalid-feedback">
  188. Please select city.
  189. </div>
  190. </div>
  191. <div class="form-item col-sm-12 mb-3 is-required">
  192. <label for="streetAddress">StreetAddress</label>
  193. <input class="form-control" id="streetAddress">
  194. <div class="invalid-feedback">
  195. Please enter streetAddress.
  196. </div>
  197. </div>
  198. <div class="form-item col-sm-12 mb-3">
  199. <label for="streetAddress2">StreetAddress2</label>
  200. <input class="form-control" id="streetAddress2">
  201. <div class="invalid-feedback">
  202. Please enter streetAddress.
  203. </div>
  204. </div>
  205. <div class="form-item col-sm-12 mb-3 is-required">
  206. <label for="postal-code">Post Code</label>
  207. <input class="form-control" id="postal-code">
  208. <div class="invalid-feedback">
  209. Please enter postal code.
  210. </div>
  211. </div>
  212. <div class="form-item col-sm-12 mb-3 is-required">
  213. <label for="cc-number">Credit card number</label>
  214. <div class="form-control" id="cc-number"></div>
  215. <div class="invalid-feedback">
  216. Credit card number is required
  217. </div>
  218. </div>
  219. <div class="form-item col-sm-12 mb-3 is-required">
  220. <label for="cc-expiration">Expiration</label>
  221. <div class="form-control" id="cc-expiration"></div>
  222. <div class="invalid-feedback">
  223. Expiration date required
  224. </div>
  225. </div>
  226. <div class="form-item col-sm-12 mb-3 is-required">
  227. <label for="cc-expiration">CVV</label>
  228. <div class="form-control" id="cc-cvv"></div>
  229. <div class="invalid-feedback">
  230. Security code required
  231. </div>
  232. </div>
  233. </div>
  234. <hr class="mb-4">
  235. <div class="text-center">
  236. <!-- <button class="btn btn-primary btn-lg" type="submit">Pay with <span id="card-brand">Card</span></button>-->
  237. <button class="btn btn-primary btn-lg" type="submit">Pay</button>
  238. </div>
  239. </form>
  240. </div>
  241. </div>
  242. <script>
  243. // 数据
  244. let curPaymentMethod = '';
  245. const paymentMethods = {
  246. 'CreditCard': 'credit_card',
  247. 'PayPalAccount': 'paypal_account',
  248. 'VenmoAccount': 'venmo_account',
  249. 'ApplePayCard': 'apple_pay_card',
  250. 'AndroidPayCard': 'android_pay_card', //google pay
  251. }
  252. const token = document.getElementById('_token').value;
  253. const amount = document.getElementById('_amt').value;
  254. const intent = document.getElementById('_intent').value;
  255. const currency = document.getElementById('_cur').value;
  256. const transactionNo = document.getElementById('_transactionNo').value;
  257. // const url = document.getElementById("_submitUrl").value;
  258. const vendor = document.getElementById("_vendor").value;
  259. const url = '/bt/process';
  260. // const vendor = 'creditcard';
  261. //方法
  262. function ajaxObject() {
  263. let xmlHttp;
  264. try {
  265. // Firefox, Opera 8.0+, Safari
  266. xmlHttp = new XMLHttpRequest();
  267. }
  268. catch (e) {
  269. // Internet Explorer
  270. try {
  271. xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
  272. } catch (e) {
  273. try {
  274. xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
  275. } catch (e) {
  276. alert("您的浏览器不支持AJAX!");
  277. return false;
  278. }
  279. }
  280. }
  281. return xmlHttp;
  282. }
  283. function ajaxPost(url, data, fnSucceed) {
  284. $.ajax({
  285. type: "POST",
  286. url: url,
  287. data: data,
  288. contentType: "application/x-www-form-urlencoded",
  289. dataType: "json",
  290. success: function (data) {
  291. console.log("success");
  292. fnSucceed(data);
  293. },
  294. error: function (data) {
  295. console.log("error");
  296. },
  297. })
  298. }
  299. function getVendor() {
  300. // 根据vendor 配置braintree 的支付渠道
  301. let curVendor = null;
  302. console.log('vendor is: ', vendor);
  303. switch (vendor) {
  304. case 'paypal':
  305. curVendor = {
  306. paypal: {
  307. flow: 'checkout',
  308. amount: amount,
  309. currency: currency
  310. },
  311. // paypalCredit: {
  312. // flow: 'checkout',
  313. // amount: amount,
  314. // currency: currency
  315. // },
  316. };
  317. break
  318. case 'venmo':
  319. curVendor = {
  320. venmo: { // 只会在移动端出现
  321. allowNewBrowserTab: false
  322. }
  323. };
  324. break
  325. case 'googlepay':
  326. curVendor = {
  327. card: false, // 不显示信用卡
  328. googlePay: {
  329. // googlePayVersion: 2,
  330. // merchantId: 'merchant-id-from-google',
  331. transactionInfo: {
  332. totalPriceStatus: 'FINAL',
  333. totalPrice: amount,
  334. currencyCode: currency
  335. },
  336. allowedPaymentMethods: [{
  337. type: 'CARD',
  338. parameters: {
  339. // We recommend collecting and passing billing address information with all Google Pay
  340. // transactions as a best practice.
  341. billingAddressRequired: true,
  342. billingAddressParameters: {
  343. format: 'FULL'
  344. }
  345. }
  346. }]
  347. },
  348. };
  349. break
  350. case 'applepay':
  351. curVendor = {
  352. applePay: {
  353. displayName: 'Merchant Name',
  354. paymentRequest: {
  355. label: 'Localized Name',
  356. currencyCode: '',
  357. merchantCapabilities: '',
  358. total: amount
  359. }
  360. },
  361. };
  362. break
  363. case 'creditcard':
  364. break
  365. default:
  366. console.log('vendor is: ', vendor);
  367. }
  368. console.log('curVendor info: ', curVendor);
  369. return curVendor
  370. }
  371. function handleIdElementStyle(id, attr, val) {
  372. const el = document.getElementById(id);
  373. if (el) {
  374. el.style[attr] = val;
  375. }
  376. }
  377. function initClient() {
  378. const curVendor = getVendor();
  379. const options = {
  380. authorization: token,
  381. container: '#dropin-container',
  382. ...curVendor
  383. };
  384. console.log('options', options);
  385. var form = $('form');
  386. braintree.client.create({
  387. authorization: token,
  388. }, function (err, clientInstance) {
  389. if (err) {
  390. console.error(err);
  391. alert('clientInstance: ' + JSON.stringify(err));
  392. return;
  393. }
  394. braintree.dataCollector.create({
  395. client: clientInstance,
  396. paypal: true
  397. }, function (err, dataCollectorInstance) {
  398. if (err) {
  399. // Handle error in creation of data collector
  400. console.error(err);
  401. alert('clientInstance: ' + JSON.stringify(err));
  402. return;
  403. }
  404. // At this point, you should access the dataCollectorInstance.deviceData value and provide it
  405. // to your server, e.g. by injecting it into your form as a hidden input.
  406. var deviceData = dataCollectorInstance.deviceData;
  407. braintree.hostedFields.create({
  408. client: clientInstance,
  409. styles: {
  410. input: {
  411. // change input styles to match
  412. // bootstrap styles
  413. 'font-size': '1rem',
  414. color: '#495057'
  415. }
  416. },
  417. fields: {
  418. // cardholderName: {
  419. // selector: '#cc-name',
  420. // placeholder: 'Name as it appears on your card'
  421. // },
  422. number: {
  423. selector: '#cc-number',
  424. placeholder: '4111 1111 1111 1111'
  425. },
  426. cvv: {
  427. selector: '#cc-cvv',
  428. placeholder: '123'
  429. },
  430. expirationDate: {
  431. selector: '#cc-expiration',
  432. placeholder: 'MM / YYYY'
  433. }
  434. }
  435. }, function (err, hostedFieldsInstance) {
  436. if (err) {
  437. console.error('hostedFieldsInstance: ' + err);
  438. return;
  439. }
  440. function createInputChangeEventListener(element) {
  441. return function () {
  442. validateInput(element);
  443. }
  444. }
  445. function setValidityClasses(element, validity) {
  446. if (validity) {
  447. element.removeClass('is-invalid');
  448. element.addClass('is-valid');
  449. } else {
  450. element.addClass('is-invalid');
  451. element.removeClass('is-valid');
  452. }
  453. }
  454. function validateInput(element) {
  455. // very basic validation, if the
  456. // fields are empty, mark them
  457. // as invalid, if not, mark them
  458. // as valid
  459. if (!element.val().trim()) {
  460. setValidityClasses(element, false);
  461. return false;
  462. }
  463. setValidityClasses(element, true);
  464. return true;
  465. }
  466. var email = $('#email');
  467. function validateEmail() {
  468. var baseValidity = validateInput(email);
  469. if (!baseValidity) {
  470. return false;
  471. }
  472. if (email.val().indexOf('@') === -1) {
  473. setValidityClasses(email, false);
  474. return false;
  475. }
  476. setValidityClasses(email, true);
  477. return true;
  478. }
  479. $('.form-item.is-required input').on('blur change', function () {
  480. var $this = $(this);
  481. if ($this.type == 'email') {
  482. validateEmail($this);
  483. }
  484. validateInput($this);
  485. })
  486. $('.form-item.is-required select').on('blur change', function () {
  487. var $this = $(this);
  488. validateInput($this);
  489. })
  490. hostedFieldsInstance.on('validityChange', function (event) {
  491. var field = event.fields[event.emittedBy];
  492. // Remove any previously applied error or warning classes
  493. $(field.container).removeClass('is-valid');
  494. $(field.container).removeClass('is-invalid');
  495. if (field.isValid) {
  496. $(field.container).addClass('is-valid');
  497. } else if (field.isPotentiallyValid) {
  498. // skip adding classes if the field is
  499. // not valid, but is potentially valid
  500. } else {
  501. $(field.container).addClass('is-invalid');
  502. }
  503. });
  504. hostedFieldsInstance.on('cardTypeChange', function (event) {
  505. var cardBrand = $('#card-brand');
  506. var cvvLabel = $('[for="cc-cvv"]');
  507. if (event.cards.length === 1) {
  508. var card = event.cards[0];
  509. // change pay button to specify the type of card
  510. // being used
  511. cardBrand.text(card.niceType);
  512. // update the security code label
  513. cvvLabel.text(card.code.name);
  514. } else {
  515. // reset to defaults
  516. cardBrand.text('Card');
  517. cvvLabel.text('CVV');
  518. }
  519. });
  520. form.submit(function (event) {
  521. event.preventDefault();
  522. //显示loading
  523. handleIdElementStyle('loading', 'display', 'block');
  524. var formIsInvalid = false;
  525. // perform validations on the non-Hosted Fields
  526. // inputs
  527. if (!validateEmail()) {
  528. formIsInvalid = true;
  529. }
  530. var $inputs = $('.form-item.is-required input')
  531. for (var i = 0; i < $inputs.length; i++) {
  532. if (!validateInput($inputs.eq(i))) {
  533. formIsInvalid = true
  534. }
  535. }
  536. var $selects = $('.form-item.is-required select')
  537. for (var i = 0; i < $selects.length; i++) {
  538. if (!validateInput($selects.eq(i))) {
  539. formIsInvalid = true
  540. }
  541. }
  542. var state = hostedFieldsInstance.getState();
  543. // Loop through the Hosted Fields and check
  544. // for validity, apply the is-invalid class
  545. // to the field container if invalid
  546. Object.keys(state.fields).forEach(function (field) {
  547. if (!state.fields[field].isValid) {
  548. $(state.fields[field].container).addClass('is-invalid');
  549. formIsInvalid = true;
  550. }
  551. });
  552. if (formIsInvalid) {
  553. // skip tokenization request if any fields are invalid
  554. //隐藏loading
  555. handleIdElementStyle('loading', 'display', 'none');
  556. return;
  557. }
  558. const firstName = document.getElementById('first-name').value;
  559. const lastName = document.getElementById('last-name').value;
  560. const countryName = document.getElementById('countryId').value;
  561. const stateName = document.getElementById('stateId').value;
  562. const city = document.getElementById('cityId').value;
  563. const address = document.getElementById('streetAddress').value;
  564. const address2 = document.getElementById('streetAddress2').value;
  565. let streetAddressArr = []
  566. stateName && streetAddressArr.push(streetAddressArr)
  567. city && streetAddressArr.push(city)
  568. address && streetAddressArr.push(address)
  569. address2 && streetAddressArr.push(address2)
  570. const postalCode = document.getElementById('postal-code').value;
  571. const email = document.getElementById('email').value;
  572. const tokenizeOption = {
  573. cardholderName: firstName + ' ' + lastName,
  574. billingAddress: {
  575. firstName: firstName,
  576. lastName: lastName,
  577. cardholderName: '2333',
  578. // company: 'Company',
  579. streetAddress: streetAddressArr.join(','),
  580. postalCode: postalCode,
  581. // extendedAddress: 'Unit 1',
  582. // passing just one of the country options is sufficient to
  583. // associate the card details with a particular country
  584. // valid country names and codes can be found here:
  585. // https://developers.braintreepayments.com/reference/general/countries/ruby#list-of-countries
  586. countryName: 'United States',
  587. countryCodeAlpha2: 'US',
  588. countryCodeAlpha3: 'USA',
  589. countryCodeNumeric: '840'
  590. }
  591. };
  592. hostedFieldsInstance.tokenize(tokenizeOption, function (err, payload) {
  593. if (err) {
  594. console.error(err);
  595. alert('payload: ' + JSON.stringify(err));
  596. //隐藏loading
  597. handleIdElementStyle('loading', 'display', 'none');
  598. return;
  599. }
  600. // This is where you would submit payload.nonce to your server
  601. // $('.toast').toast('show');
  602. // you can either send the form values with the payment
  603. // method nonce via an ajax request to your server,
  604. // or add the payment method nonce to a hidden inpiut
  605. // on your form and submit the form programatically
  606. // $('#payment-method-nonce').val(payload.nonce);
  607. // form.submit()
  608. // 通过payload来给curPaymentMethod赋值
  609. curPaymentMethod = paymentMethods[payload.type];
  610. // Submit payload.nonce to your server
  611. const temp = payload;
  612. const params = "paymentMethodNonce="+payload.nonce+"&transactionNo="+transactionNo+
  613. "&paymentMethod="+curPaymentMethod+"&firstName="+firstName+"&lastName="+
  614. lastName+"&addressLine1="+streetAddress+"&postalCode="+postalCode+"&email="+email+
  615. "&deviceData=" + deviceData;
  616. ajaxPost(url, params, function (data) {
  617. const result = data;
  618. if ("success" === result.result.status) {
  619. handleIdElementStyle('loading', 'display', 'none');
  620. $('.alert-primary').show(500);
  621. setTimeout(function () {
  622. // handleIdElementStyle('main','display','block');
  623. window.location.href = "";
  624. }, 1500);
  625. } else {
  626. handleIdElementStyle('loading', 'display', 'none');
  627. handleIdElementStyle('main', 'display', 'block');
  628. alert(result.ret_msg)
  629. location.reload();
  630. }
  631. })
  632. });
  633. });
  634. });
  635. });
  636. });
  637. }
  638. //=====================start project=======================
  639. //
  640. initClient()
  641. </script>
  642. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  643. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  644. </body>
  645. </html>