Skip to content
This repository has been archived by the owner on Apr 19, 2022. It is now read-only.

Commit

Permalink
Adding AU BECS Debit (#141)
Browse files Browse the repository at this point in the history
* Initial node.js udpdates to include BECS AU including support for multi-currency which was a dependency

* Update to remove multi-currency drop down and auto change currency on Australia country select to allow AU BECS debit

* Incorporating changes to address review comments

* Added au_becs_debit to the env example file

* Adding Przelewy24 to Stripe Payments Demo (#143)

Adding P24 with PaymentIntents to Stripe Payments Demo

- Added P24 availability to `.env.example`, Node and PHP configs
- Added P24 handler and config to `payments.js`
- Added P24 section to `index.html`
- Added error handling for `requires_payment_method`
- Added last payment error message to error handler

* Publish "last payment error" from each backend server (#146)

* Add `last_payment_error` to golang backend

* Add `last_payment_error` to Ruby backend

* Add `last_payment_error` to Python backend

* Add `last_payment_error` to PHP backend

* Add `last_payment_error` to Java backend

* Add Bancontact, EPS and Giropay PaymentIntent integrations (#147)

* added p24 back to .env.example file

* Resolved missed merge updates for server/go/app.go

* Cleaned up code as per the changes recommended by willock's review

* fixed missed rebase merge

* Renamed functions, removed update_error variable, formatting updates

Co-authored-by: James Willock <[email protected]>
  • Loading branch information
rgultiano-stripe and willock-stripe authored Oct 13, 2020
1 parent 99a46e2 commit c6cb284
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ STRIPE_ACCOUNT_COUNTRY=US
# Make sure to check the docs: https://stripe.com/docs/sources
# Only used for servers that don't have a separate config/settings file.
# For Node.js see the server/node/config.js file!
PAYMENT_METHODS="alipay, bancontact, card, eps, ideal, giropay, multibanco, p24, sofort, wechat"
PAYMENT_METHODS="alipay, bancontact, card, eps, ideal, giropay, multibanco, p24, sofort, wechat, au_becs_debit"

# Optional ngrok configuration for development (if you have a paid ngrok account).
NGROK_SUBDOMAIN=
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ typings/

# serverless
serverless/
.vscode/launch.json
18 changes: 18 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ <h2>Payment Information</h2>
<input type="radio" name="payment" id="payment-wechat" value="wechat">
<label for="payment-wechat">WeChat Pay</label>
</li>
<li>
<input type="radio" name="payment" id="payment-au_becs_debit" value="au_becs_debit">
<label for="payment-au_becs_debit">BECS Direct Debit</label>
</li>
</ul>
</nav>
<div class="payment-info card visible">
Expand Down Expand Up @@ -182,11 +186,25 @@ <h2>Payment Information</h2>
<div id="wechat-qrcode"></div>
<p class="notice">Click the button below to generate a QR code for WeChat.</p>
</div>
<div class="payment-info au_becs_debit">
<fieldset>
<label>
<span>Bank Account</span>
<div id="becs-bank-element" class="field"></div>
</label>
</fieldset>
<p class="notice">By providing your bank account details and confirming this payment, you agree to this Direct Debit Request and
the Direct Debit Request service agreement, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID
number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of Stripe Payments Demo
(the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account
holder or an authorised signatory on the account listed above.</p>
</div>
</section>
<button class="payment-button" type="submit">Pay</button>
</form>
<div id="card-errors" class="element-errors"></div>
<div id="iban-errors" class="element-errors"></div>
<div id="becs-errors" class="element-errors"></div>
</div>
<div id="confirmation">
<div class="status processing">
Expand Down
153 changes: 131 additions & 22 deletions public/javascripts/payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

// Retrieve the configuration for the store.
const config = await store.getConfig();
let activeCurrency = config.currency;

// Create references to the main form and its submit button.
const form = document.getElementById('payment-form');
Expand Down Expand Up @@ -131,6 +132,33 @@
// Mount the iDEAL Bank Element on the page.
idealBank.mount('#ideal-bank-element');

/**
* Add a BECS element that matches the look-and-feel of the app.
*
* This allows you to collect australian bank account details
*/

const becsBank = elements.create('auBankAccount', {style});

// Mount the BECS element on the page.
becsBank.mount('#becs-bank-element');

// Monitor change events on the BECS Element to display any errors.
becsBank.on('change', ({error, bankName}) => {
const becsBankErrors = document.getElementById('becs-errors');
if (error) {
becsBankErrors.textContent = error.message;
becsBankErrors.classList.add('visible');
} else {
becsBankErrors.classList.remove('visible');
if (bankName) {
updateButtonLabel('au_becs_debit', bankName);
}
}
// Re-enable the Pay button.
submitButton.disabled = false;
});

/**
* Implement a Stripe Payment Request Button Element.
*
Expand Down Expand Up @@ -215,7 +243,7 @@
});
const amount = store.formatPrice(
response.paymentIntent.amount,
config.currency
activeCurrency
);
updateSubmitButtonPayText(`Pay ${amount}`);
});
Expand Down Expand Up @@ -283,6 +311,20 @@
submitButton.disabled = true;
submitButton.textContent = 'Processing…';

// Update Payment Intent if currency is different to default
if (config.currency !== activeCurrency) {
const response = await store.updatePaymentIntentCurrency(
paymentIntent.id,
activeCurrency,
[payment]
);

if (response.error) {
handleError(response);
return;
}
}

if (payment === 'card') {
// Let Stripe.js handle the confirmation of the PaymentIntent with the card Element.
const response = await stripe.confirmCardPayment(
Expand Down Expand Up @@ -321,8 +363,8 @@
payment_method: {
billing_details: {
name,
email
}
email,
},
},
return_url: window.location.href,
}
Expand All @@ -337,8 +379,8 @@
ideal: idealBank,
billing_details: {
name,
email
}
email,
},
},
return_url: window.location.href,
}
Expand All @@ -351,7 +393,7 @@
payment_method: {
billing_details: {
name,
}
},
},
return_url: window.location.href,
}
Expand All @@ -364,7 +406,7 @@
payment_method: {
billing_details: {
name,
}
},
},
return_url: window.location.href,
}
Expand All @@ -377,7 +419,7 @@
payment_method: {
billing_details: {
name,
}
},
},
return_url: window.location.href,
}
Expand All @@ -390,12 +432,26 @@
payment_method: {
billing_details: {
name,
}
},
},
return_url: window.location.href,
}
);
handlePayment(response);
} else if (payment == 'au_becs_debit') {
const response = await stripe.confirmAuBecsDebitPayment(
paymentIntent.client_secret,
{
payment_method: {
au_becs_debit: becsBank,
billing_details: {
name,
email,
},
},
}
);
handlePayment(response);
} else {
// Prepare all the Stripe source common data.
const sourceData = {
Expand Down Expand Up @@ -434,6 +490,7 @@
const {source} = await stripe.createSource(sourceData);
handleSourceActivation(source);
}

});

// Handle new PaymentIntent result
Expand Down Expand Up @@ -472,7 +529,8 @@
} else if (paymentIntent.status === 'requires_payment_method') {
// Failure. Requires new PaymentMethod, show last payment error message.
mainElement.classList.remove('processing');
confirmationElement.querySelector('.error-message').innerText = paymentIntent.last_payment_error || 'Payment failed';
confirmationElement.querySelector('.error-message').innerText =
paymentIntent.last_payment_error || 'Payment failed';
mainElement.classList.add('error');
} else {
// Payment has failed.
Expand All @@ -483,6 +541,27 @@
}
};

const handleError = (updateResponse) => {
// handle any error
const {paymentIntent, error} = updateResponse;

const mainElement = document.getElementById('main');
const confirmationElement = document.getElementById('confirmation');

if (error && error.type === 'validation_error') {
mainElement.classList.remove('processing');
mainElement.classList.remove('receiver');
submitButton.disabled = false;
submitButton.textContent = submitButtonPayText;
} else if (error) {
mainElement.classList.remove('processing');
mainElement.classList.remove('receiver');
confirmationElement.querySelector('.error-message').innerText =
error.message;
mainElement.classList.add('error');
}
};

// Handle activation of payment sources not yet supported by PaymentIntents
const handleSourceActivation = (source) => {
const mainElement = document.getElementById('main');
Expand All @@ -505,7 +584,7 @@
form.querySelector('.payment-info.wechat p').style.display = 'none';
let amount = store.formatPrice(
store.getPaymentTotal(),
config.currency
activeCurrency
);
updateSubmitButtonPayText(
`Scan this QR code on WeChat to pay ${amount}`
Expand All @@ -530,7 +609,7 @@
const receiverInfo = confirmationElement.querySelector(
'.receiver .info'
);
let amount = store.formatPrice(source.amount, config.currency);
let amount = store.formatPrice(source.amount, activeCurrency);
switch (source.type) {
case 'ach_credit_transfer':
// Display the ACH Bank Transfer information to the user.
Expand Down Expand Up @@ -592,9 +671,12 @@
*/
const paymentIntentTerminalState = ({status, last_payment_error}) => {
const endStates = ['succeeded', 'processing', 'canceled'];
const hasError = typeof last_payment_error !== "undefined";
const hasError = typeof last_payment_error !== 'undefined';

return endStates.includes(status) || (status === 'requires_payment_method' && hasError);
return (
endStates.includes(status) ||
(status === 'requires_payment_method' && hasError)
);
};

/**
Expand All @@ -612,15 +694,18 @@
start = null
) => {
start = start ? start : Date.now();
const endStates = [
'succeeded',
'processing',
'canceled',
'requires_payment_method',
];
// Retrieve the PaymentIntent status from our server.
const rawResponse = await fetch(`payment_intents/${paymentIntent}/status`);
const response = await rawResponse.json();
const isTerminalState = paymentIntentTerminalState(response.paymentIntent);

if (
!isTerminalState &&
Date.now() < start + timeout
) {
if (!isTerminalState && Date.now() < start + timeout) {
// Not done yet. Let's wait and check again.
setTimeout(
pollPaymentIntentStatus,
Expand All @@ -643,7 +728,10 @@
const mainElement = document.getElementById('main');

if (url.searchParams.get('payment_intent')) {
if (url.searchParams.get('source') && url.searchParams.get('client_secret')) {
if (
url.searchParams.get('source') &&
url.searchParams.get('client_secret')
) {
mainElement.classList.add('checkout', 'success', 'processing');
}
// Poll the PaymentIntent status.
Expand Down Expand Up @@ -770,11 +858,17 @@
'usd',
],
},
au_becs_debit: {
name: 'BECS Direct Debit',
flow: 'none',
countries: ['AU'],
currencies: ['aud'],
},
};

// Update the main button to reflect the payment method being selected.
const updateButtonLabel = (paymentMethod, bankName) => {
let amount = store.formatPrice(store.getPaymentTotal(), config.currency);
let amount = store.formatPrice(store.getPaymentTotal(), activeCurrency);
let name = paymentMethods[paymentMethod].name;
let label = `Pay ${amount}`;
if (paymentMethod !== 'card') {
Expand All @@ -783,9 +877,10 @@
if (paymentMethod === 'wechat') {
label = `Generate QR code to pay ${amount} with ${name}`;
}
if (paymentMethod === 'sepa_debit' && bankName) {
if (['sepa_debit', 'au_becs_debit'].includes(paymentMethod) && bankName) {
label = `Debit ${amount} from ${bankName}`;
}

updateSubmitButtonPayText(label);
};

Expand All @@ -794,6 +889,13 @@
selector.querySelector(`option[value=${country}]`).selected = 'selected';
selector.className = `field ${country.toLowerCase()}`;

//update currency if there's a currency for that country
if (country === 'AU') {
activeCurrency = 'aud';
} else {
activeCurrency = config.currency;
}

// Trigger the methods to show relevant fields and payment methods on page load.
showRelevantFormFields();
showRelevantPaymentMethods();
Expand Down Expand Up @@ -852,6 +954,7 @@
if (!country) {
country = form.querySelector('select[name=country] option:checked').value;
}

const paymentInputs = form.querySelectorAll('input[name=payment]');
for (let i = 0; i < paymentInputs.length; i++) {
let input = paymentInputs[i];
Expand All @@ -860,7 +963,7 @@
input.value === 'card' ||
(config.paymentMethods.includes(input.value) &&
paymentMethods[input.value].countries.includes(country) &&
paymentMethods[input.value].currencies.includes(config.currency))
paymentMethods[input.value].currencies.includes(activeCurrency))
);
}

Expand All @@ -878,6 +981,9 @@
form.querySelector('.payment-info.sepa_debit').classList.remove('visible');
form.querySelector('.payment-info.wechat').classList.remove('visible');
form.querySelector('.payment-info.redirect').classList.remove('visible');
form
.querySelector('.payment-info.au_becs_debit')
.classList.remove('visible');
updateButtonLabel(paymentInputs[0].value);
};

Expand All @@ -904,6 +1010,9 @@
form
.querySelector('.payment-info.wechat')
.classList.toggle('visible', payment === 'wechat');
form
.querySelector('.payment-info.au_becs_debit')
.classList.toggle('visible', payment === 'au_becs_debit');
form
.querySelector('.payment-info.redirect')
.classList.toggle('visible', flow === 'redirect');
Expand Down
Loading

0 comments on commit c6cb284

Please sign in to comment.