Last active
January 19, 2024 01:56
-
-
Save davidmdem/05994bd0880a30acf153bcbf6f0cf093 to your computer and use it in GitHub Desktop.
A basic demonstration of the Blackbaud Checkout flow for Advanced Donation Forms.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div id="checkout-adf"> | |
| <h5> | |
| {{fund.Name}} | |
| </h5> | |
| <p> | |
| {{fund.Description}} | |
| </p> | |
| <hr /> | |
| <div v-if="!confirmationHtml" style="float:left; width: 200px" > | |
| <label>Amount</label> | |
| <input v-model="fund.Amount" /> | |
| <label>First Name</label> | |
| <input v-model="Donor.FirstName" /> | |
| <label>Last Name</label> | |
| <input v-model="Donor.LastName" /> | |
| <label>Email</label> | |
| <input v-model="Donor.EmailAddress" /> | |
| <label>Street Address</label> | |
| <input v-model="Donor.Address.StreetAddress" /> | |
| <label>City</label> | |
| <input v-model="Donor.Address.City" /> | |
| <label>State</label> | |
| <input v-model="Donor.Address.State" /> | |
| <label>Country</label> | |
| <input v-model="Donor.Address.Country" /> | |
| <label>State</label> | |
| <input v-model="Donor.Address.PostalCode" /> | |
| <br/> | |
| <br/> | |
| <a href="#" @click.prevent="checkout">Checkout</a> | |
| </div> | |
| <div v-if="confirmationHtml" v-html="confirmationHtml"></div> | |
| <div style="float:right; width: 750px"> | |
| <h6>Donation</h6> | |
| <pre><code>{{Donation}}</code></pre> | |
| <br /> | |
| <br /> | |
| <h6>Checkout Data</h6> | |
| <pre><code>{{CheckoutData}}</code></pre> | |
| <br /> | |
| <br /> | |
| <h6>Content Info</h6> | |
| <pre><code>{{contentInfo}}</code></pre> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/vue"></script> | |
| <script src="https://dev.forms.iuf.iu.edu/checkout-adf.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function() { | |
| /** | |
| * A shared instance of `DonationService`. | |
| */ | |
| var ds = {}; | |
| /** | |
| * Checkout Payment popup is loaded in this form. | |
| * | |
| * Normally, BBIS wants to have only a single `<form>` element per page. | |
| * A `<form>` placed into the ADF's Design section will not | |
| * be rendered. So we insert on the client side. | |
| * | |
| * Note the `data-disable-submit='true'` attribute. This is required for the `handleCheckoutComplete` | |
| * method to fire. If it is not set the page will refresh with a transaction ID in the URL instead. | |
| */ | |
| function AppendCheckoutForm() { | |
| var form = | |
| "<form method='get' id=\"paymentForm\" data-formtype='bbCheckout' data-disable-submit='true' novalidate></form>"; | |
| $("body").append(form); | |
| } | |
| /** | |
| * Helper function so I can treat the donation service calls like a Promise instead | |
| * of using the success/error callbacks. | |
| * | |
| * These wrappers are *not* required. I am doing it here so that I can use async/await | |
| * syntax. It makes the code in the handlers a lot more straight forward to follow. | |
| */ | |
| function NewPromise() { | |
| var promise = $.Deferred(); | |
| promise.resolver = (data, error) => { | |
| return error ? promise.reject(error.message) : promise.resolve(data); | |
| }; | |
| return promise; | |
| } | |
| function GetContentInfo(partId) { | |
| var promise = NewPromise(); | |
| ds.getADFEditorContentInformation(partId, promise.resolver, promise.resolver); | |
| return promise; | |
| } | |
| function GetPublicKey() { | |
| var promise = NewPromise(); | |
| ds.getCheckoutPublicKey(promise.resolver, promise.resolver); | |
| return promise; | |
| } | |
| function ValidateDonation(donation) { | |
| var promise = NewPromise(); | |
| ds.validateDonationRequest(donation, promise.resolver, promise.resolver); | |
| return promise; | |
| } | |
| function CheckoutDonationCreate(donation) { | |
| var promise = NewPromise(); | |
| ds.checkoutDonationCreate(donation, promise.resolver, promise.resolver); | |
| return promise; | |
| } | |
| function CheckoutDonationCancel(donation) { | |
| var promise = NewPromise(); | |
| ds.checkoutDonationCancel(donation, promise.resolver, promise.resolver); | |
| return promise; | |
| } | |
| function CheckoutDonationComplete(donation) { | |
| var promise = NewPromise(); | |
| ds.checkoutDonationComplete(donation, promise.resolver, promise.resolver); | |
| return promise; | |
| } | |
| // Initialize a single Vue instance/application within the page. | |
| var app = new Vue({ | |
| el: "#checkout-adf", | |
| /** | |
| * The object returned by `data` is bound to the view template inside #checkout-adf | |
| * and is accessible in methods as children of `this`. | |
| */ | |
| data() { | |
| return { | |
| partId: null, | |
| publicKey: null, | |
| transactionToken: null, | |
| confirmationHtml: null, | |
| contentInfo: {}, | |
| // Hard coding a single fund for demonstration. | |
| // This could be populated with an ajax call in `mounted` or selected from a list, etc. | |
| fund: { | |
| DesignationId: "35287a05-6672-4ddb-8345-236ea6e88ed5", | |
| Name: "Indiana University Priority Needs", | |
| Description: | |
| "Gifts to this account will be used to support grants, awards, scholarships or any other purpose consistent with the mission of the Foundation or Indiana University.", | |
| Amount: 20 | |
| }, | |
| // The inputs on the page bind their values directly to this model | |
| // as the user fills out the form. You can pre-populate these values | |
| // here for more rapid testing. | |
| Donor: { | |
| FirstName: "Test", | |
| LastName: "Person", | |
| EmailAddress: "", | |
| Address: { | |
| StreetAddress: "123 Fake St.", | |
| City: "Bloomington", | |
| State: "Indiana", | |
| Country: "United States", | |
| PostalCode: "47401" | |
| } | |
| } | |
| }; | |
| }, | |
| /** | |
| * Called when the Vue component first loads. | |
| */ | |
| async mounted() { | |
| console.log("checkout-adf mounted", new Date().toISOString()); | |
| // Populate `this.partId` and initialize the DonationService. | |
| this.partId = $(".BBDonationApiContainer").data("partid"); | |
| ds = new BLACKBAUD.api.DonationService(this.partId); | |
| // Get configuration info for this specific ADF part. | |
| // https://developer.blackbaud.com/bbis/reference/rest/#donationeditorpartid | |
| this.contentInfo = await GetContentInfo(this.partId); | |
| // Put a bbCheckout form on the page. | |
| AppendCheckoutForm(); | |
| }, | |
| methods: { | |
| /** | |
| * Called when the user clicks `checkout`. | |
| */ | |
| async checkout() { | |
| console.log("checkout start"); | |
| // Validate the donation object. | |
| // https://developer.blackbaud.com/bbis/reference/rest/#partiddonationvalidatedonationrequest | |
| var validationResponse = await ValidateDonation(this.Donation); | |
| console.log("validationResponse", validationResponse); | |
| if (validationResponse !== true) { | |
| this.handleError("Could not validate donation object."); | |
| return false; | |
| } | |
| // Get a public key required to bring up the BBPS payment modal. | |
| // https://developer.blackbaud.com/bbis/reference/rest/#adfcheckoutpublickey | |
| var publicKeyResponse = await GetPublicKey(); | |
| this.publicKey = JSON.parse(publicKeyResponse.Data).PublicKey; | |
| console.log("received public key", this.publicKey); | |
| // Setup payment modal with callbacks. | |
| var checkout = new SecureCheckout( | |
| this.handleCheckoutComplete, | |
| this.handleCheckoutError, | |
| this.handleCheckoutCancelled, | |
| this.handleCheckoutLoaded | |
| ); | |
| // Show the modal. | |
| checkout.processCardNotPresent(this.CheckoutData); | |
| }, | |
| /** | |
| * When the checkout modal opens. | |
| */ | |
| async handleCheckoutLoaded(event) { | |
| // Get transactionId from the modal Iframe URL | |
| var url = $("#bbCheckoutPaymentIframe").prop("src"); | |
| var tid = BLACKBAUD.api.querystring.getQueryStringValue("t", url); | |
| this.transactionToken = tid; | |
| // Create the donation | |
| // https://developer.blackbaud.com/bbis/reference/rest/#adfcheckoutcreate | |
| await CheckoutDonationCreate(this.Donation); | |
| }, | |
| /** | |
| * After the payment is processed and the modal closes. | |
| * | |
| * @param {*} event | |
| * @param {*} tranToken | |
| */ | |
| async handleCheckoutComplete(event, tranToken) { | |
| $("#bbCheckoutOverlayIframe").show(); | |
| if (tranToken) { | |
| this.transactionToken = tranToken; | |
| console.log("handleCheckoutComplete", tranToken, this.Donation); | |
| // Transitions the checkout item from Pending to Complete | |
| // https://developer.blackbaud.com/bbis/reference/rest/#adfcheckoutcomplete | |
| var checkoutCompleteResponse = await CheckoutDonationComplete(this.Donation); | |
| console.log("completeResponse", checkoutCompleteResponse); | |
| // Pull out the confirmation HTML to display to the user. | |
| this.confirmationHtml = JSON.parse(checkoutCompleteResponse.Data).confirmationHTML; | |
| } else { | |
| this.handleError("Checkout complete did not have a transaction token."); | |
| } | |
| $("#bbCheckoutOverlayIframe").hide(); | |
| }, | |
| /** | |
| * Error handler. Not doing much in this demo. | |
| * @param {*} event | |
| * @param {*} errorMsg | |
| * @param {*} errorCode | |
| */ | |
| handleCheckoutError(event, errorMsg, errorCode) { | |
| console.log("handleCheckoutError", errorMsg, errorCode, event); | |
| this.handleError(errorMsg); | |
| }, | |
| /** | |
| * Cancel donation if user closed popup. | |
| * @param {*} event | |
| */ | |
| async handleCheckoutCancelled(event) { | |
| console.log("handleCheckoutCancelled", event); | |
| var cancelResponse = await CheckoutDonationCancel(this.Donation); | |
| console.log("cancelResponse", cancelResponse); | |
| $(document).unbind("checkoutComplete"); | |
| $(document).unbind("checkoutLoaded"); | |
| $(document).unbind("checkoutError"); | |
| $(document).unbind("checkoutCancel"); | |
| }, | |
| /** | |
| * Show errors/instructions to the user. | |
| * @param {*} msg | |
| */ | |
| handleError(msg) { | |
| $("#bbCheckoutOverlayIframe").hide(); | |
| console.log("handleError", msg); | |
| } | |
| }, | |
| /** | |
| * These computed objects are what gets submitted to the REST endpoints. | |
| * Thier values are populated as the form is completed. | |
| */ | |
| computed: { | |
| // Used for validate, create, complete, delete. | |
| Donation() { | |
| return { | |
| Donor: this.Donor, | |
| Gift: { | |
| Designations: [ | |
| { | |
| DesignationId: this.fund.DesignationId, | |
| Amount: this.fund.Amount | |
| } | |
| ] | |
| }, | |
| Origin: { | |
| PageId: BLACKBAUD.netcommunity.PageID, | |
| PageName: "Checkout ADF" | |
| }, | |
| TokenId: this.transactionToken, | |
| PartId: this.partId, | |
| MerchantAccountId: this.contentInfo.MerchantAccountID, | |
| BBSPReturnUri: window.location.href, | |
| BBSPTemplateSitePageId: BLACKBAUD.netcommunity.PageID | |
| }; | |
| }, | |
| // Used to call the payment api to open the checkout pop up with parameters. | |
| // The parameter for `SecureCheckout.processCardNotPresent`. | |
| CheckoutData() { | |
| return { | |
| ClientAppName: "BBIS", | |
| IsEmailRequired: true, | |
| IsNameVisible: true, | |
| key: this.publicKey, | |
| Amount: this.fund.Amount, | |
| Cardholder: this.Donor.FirstName + " " + this.Donor.LastName, | |
| BillingAddressFirstName: this.Donor.FirstName, | |
| BillingAddressLastName: this.Donor.LastName, | |
| BillingAddressCity: this.Donor.Address.City, | |
| BillingAddressCountry: this.Donor.Address.Country, | |
| BillingAddressLine: this.Donor.Address.StreetAddress, | |
| BillingAddressPostCode: this.Donor.Address.PostalCode, | |
| BillingAddressState: this.Donor.Address.State, | |
| UseCaptcha: this.contentInfo.RecaptchRequired, | |
| MerchantAccountId: this.contentInfo.MerchantAccountID, | |
| PrimaryColor: this.contentInfo.PrimaryFontColor, | |
| SecondaryColor: this.contentInfo.SecondaryFontColor, | |
| FontFamily: this.contentInfo.FontType, | |
| UseVisaCheckout: this.contentInfo.UseVisaPass, | |
| UseMasterpass: this.contentInfo.UseMasterPass, | |
| UseApplePay: this.contentInfo.UseApplePay | |
| }; | |
| } | |
| } | |
| }); | |
| })(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Live Demo (this demo will probably not stay live for long)
Setup:
checkout-adf.jsfile and update the<script>tag at the bottom ofcheckout-adf-design.html.checkout-adf-design.htmlinto the ADF part'sDesignsection.Notes:
async/await(no IE, see above)