Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@wpay/frames
Advanced tools
To install the dev kit, ensure you have npm installed and run
npm install @wpay/frames --save
Developer note: The above is the target experience. Until the package is deployed to an npm repo, please use
npm link
to link the@wpay/frames
module to your project.In the @wpay/frames project run
npm link
In your project run
npm link @wpay/frames
You should now be able to use the sdk.
Add the sdk to the page
<script src="./node_modules/@wpay/frames-sdk/dist/framesSDK.js"></script>
Add a script tag to the page, initialise the SDK and log into the payment platform.
<script>
const sdk = new FRAMES.FramesSDK({
apiKey: 'YOUR_API_KEY',
authToken: 'YOUR_AUTH_TOKEN' // Format: Bearer token_value,
apiBase: "https://dev.mobile-api.woolworths.com.au/wow/v1/pay/instore",
logLevel: FRAMES.LogLevel.DEBUG
});
</script>
Start a new card capture action. The action will handle all interactions with your elements, including their creation, validation and submission.
let action = sdk.createAction(FRAMES.ActionTypes.CaptureCard);
action.start();
This will initialise a new card capture action. This call will need to be repeated between subsequent card captures.
Add the credit card capture frames to the page.
The SDK attaches new elements to div
placeholders within your page using the element id
.
Add an element to your page.
<div id="cardElement"></div>
After adding your placeholder you can now create your frames element. When creating an element pass in the type of the element you would like to create and the id of the dom element that you would like to attach it to.
action.createFramesControl('CardGroup', 'cardElement');
Loading the page should now display the credit card capture element, displaying card, expiry date and CVV.
Submitting the page
Once the user has entered their credit card details, you are going to want to save the details. To do this, add a Submit button to the page, calling the submit
function on the action. This will run the card validation, submitting the form if succcessful.
<button onClick="async function() { await action.submit()}">Submit</button>
Once successfully submitted an action needs to be completed. Do so by calling complete.
let captureResult = await action.complete();
// The save option can be overwritten at completion time. This allows the customer
// to give input into whether a card should be saved or not post form rendering.
let captureResult = await action.complete(false);
If you would like to clear the element(s), you can also call the clear
function on the action.
<button onClick="async function() { await action.clear()}">Clear</button>
Add the sdk to the page
<script src="./node_modules/@wpay/frames/dist/framesSDK.js" />
Add a script tag to the page, initialise the SDK and log into the payment platform.
<script>
const sdk = new FRAMES.FramesSDK({
apiKey: 'YOUR_API_KEY',
authToken: 'YOUR_AUTH_TOKEN' // Format: Bearer token_value,
apiBase: "https://dev.mobile-api.woolworths.com.au/wow/v1/pay/instore",
logLevel: FRAMES.LogLevel.DEBUG
});
</script>
Start a new card step up action referencing your paymentInstrumentID and the scheme of the instrument (e.g. VISA)
let action = sdk.createAction(
FRAMES.ActionTypes.StepUp,
{
paymentInstrumentId: <YOUR PAYMENT INSTRUMENT ID>,
scheme: <PAYMENT INSTRUMENT SCHEME>
}
);
action.start();
This will initialise a new step up action. This call will need to be repeated between subsequent step up token requests.
Add the cvv element to the page.
The SDK attaches new elements to div
placeholders within your page using the element id
.
Add an element to your page.
<div id="cvvElement"></div>
After adding your placeholder you can now create your frames element. When creating an element pass in the type of the element you would like to create and the id of the dom element that you would like to attach it to.
action.createFramesControl('CardCVV', 'cvvElement');
Loading the page should now display the credit card capture element, displaying card, expiry date and CVV.
Submitting the page
Once the user has entered their CVV, you are going to want to submit and create the step up token. To do this, add a Submit button to the page, calling the submit
function on the action.
<button onClick="async function() { await action.submit()}">Submit</button>
Once successfully submitted an action needs to be completed. Do so by calling complete.
let stepUpResult = await action.complete();
The process for using individual elements is much the same as the single grouped element, however split across multiple ekements.
Instead of adding a single, control, use the action to create multiple controls.
action.createFramesControl('CardNo', 'cardCaptureCardNo', options);
action.createFramesControl('CardExpiry', 'cardCaptureExpiry', options);
action.createFramesControl('CardCVV', 'cardCaptureCVV', options);
Thats all you need to do. When you submit the action, the SDK will manage the submission of all of the individual elements for you.
Things don't always go smoothly, so sometimes there will be errors within the frames that you need to be aware of.
Here is an example of subscribing to the OnValidated
event and registering a function to handle the event (updateErrors).
Please note: The event needs to be registered on the placeholder element that the element is injected into. Registering in the wrong place may mean you miss the event.
Update errors might look something like this (Pure JS example):
async function updateErrors() {
if (action.errors()) {
document.getElementById('cardCaptureErrors').innerHTML = "<ul>"
for (error of action.errors()) {
document.getElementById('cardCaptureErrors').innerHTML += `<li>${error}</li>`;
}
document.getElementById('cardCaptureErrors').innerHTML += "</ul>"
} else {
document.getElementById('cardCaptureErrors').innerHTML = "";
}
}
Here is a basic mapping of the errors that are returned by the validation
errorMap: {
'Card Number Required': 'Please enter a valid card number.',
`Invalid Card Number`: 'Please enter a valid card number.',
'Invalid Expiry': 'Please enter a valid expiry.',
'Incomplete Expiry': 'Please enter a valid expiry',
'Expired card': 'The expiry entered is in the past. Please enter a valid expiry.',
'Invalid CVV': 'Please enter a valid CVV.'
}
If there are multiple elements on a page, there needs to be coordination to know if the form
as a whole is valid or not. The FormValid
and FormInvalid
events can be used instead of
the application having to track the validation status of each element. For example,
document.getElementById('cardElement').addEventListener(FRAMES.FramesEventType.FormValid, () => { // Do something });
document.getElementById('cardElement').addEventListener(FRAMES.FramesEventType.FormInvalid, () => { // Do something });
Sometimes you have an advanced use case like turning on and off buttons once all fields are complete which mean that you need to know when controls are visited. Typically this type of activity would be done using onFocus or onBlur events.
If you would like to listen into these events you can do so by adding an event listener to the placeholder element in much the same way as you do for validation. For example,
document
.getElementById('cardCaptureCardNo')
.addEventListener(Frames.FramesEventType.OnBlur, () => { // Do something onBlur });
The merchant's app or checkout page may wish to inject card details into the card capture session. The typical use case would be for a mobile app to have implemented OCR capabilities which would allow their customer to capture the card details using the mobile camera.
To enable this workflow, the method injectCardDetailsFromPciScopedRuntime
may be called, and as hinted by the method name, would increase the PCI-DSS obligations the merchant's implementation.
Only employ this method if the increased obligation scope has been fully understood by the implementor.
// annotated example
// create and start the SDK as per usual
let action = sdk.createAction(FRAMES.ActionTypes.CaptureCard);
await action.start();
// inject the display elements
action.createFramesControl('CardGroup', 'cardElement');
// and to inject the data into the frames
await action.injectCardDetailsFromPciScopedRuntime(
{
cardNo: '5353123412341234',
expiry: '04/20',
cvv: '999'
});
In order to ensure seamless integration with your user experience, styling can either be applied to the container via CSS, or in the case you want to make styling changes inside the frame, be injected into the Frames at run time via the options config.
An frame has several classes that can be used as targets for styling:
Here is an example of how one might use these classes to customise the style of the elements:
.woolies-element.container {
border: 1px solid #d9d9d9;
margin-left: 5px;
padding: 5px;
}
.woolies-element.error {
border: 1px solid #D0021B;
background-color: #FFECEE;
}
If you would like to further style the internal aspects of the element such as font-family/style/weight you can do so using the options object.
The options object allows you to either apply styling to all elements under the control of an action, or scope your changes to only the elements you want to change.
For instance this would set the height of the frame element top 40px and apply a font size of 30 pixels to all elements:
let options = {
"height": "40px",
"style": {
"fontSize": "30px"
}
}
If you then wanted to make the cardNo element bold, while making the other fields italic, you could do so like this:
let options = {
"height": "40px",
"cardNo": {
"style": {
"fontWeight": "bold",
"fontStyle": "normal"
}
},
"style": {
"fontSize": "30px",
"fontStyle": "italic"
}
}
The cardNo element is a little unique in that it has a sub element type that is used to show an image based on card scheme. This element can also be targeted and has an additional property allowing you to choose which side of the element it is displayed on.
This example moves the card type to the right and sets the image width to 50px to fill out the space:
let options = {
"height": "40px",
"cardNo": {
"cardType": {
"layout": "right",
"style": {
"width": "50px"
}
},
"style": {
"fontWeight": "bold",
"fontStyle": "normal"
}
},
"style": {
"fontSize": "30px",
"fontStyle": "italic"
}
}
Sometimes you want to make customisations that cant be inlined, such as change the placeholder text, or have a different colour on hover. You are able to do this by injecting CSS styling into the frame using the css property.
This example sets the placeholder colour to blue and changes it to green on hover:
let options = {
height: "40px",
"cardNo": {
"cardType": {
"layout": "right",
"style": {
"width": "50px"
}
},
"style": {
"fontWeight": "bold",
"fontStyle": "normal"
}
},
"style": {
"fontStyle": "italic",
"color": "blue",
"fontSize": "30px"
},
"css" : `
input::placeholder {
color: blue;
}
input:hover::placeholder {
color: green;
}
`
}
If you would like to see what is going on inside of the SDK, you can enable logging using the SDK constructor. Simply set the log level you would like to see and you should be able to see the log output in the console window. The log level is universal so applies to both the SDK and IFrame content.
By default, the card verification is disabled on card capture. If you would like to capture a card and enforce verification, provide a verify: true
property when initialising the capture card action.
e.g.
const action = cdk.createAction(FRAMES.ActionTypes.CaptureCard, { verify: true });
Please note: In order to use 3DS you merchant must have had 3DS enabled
The Frames SDK offers 3DS2 verification cababilities by wrapping Cardinals (https://www.cardinalcommerce.com/) 3DS songbird library and orchestrating the 3DS verification process. There are 2 supported flows, one for verification of cards during the capture process and a second for verification at time of payment.
Cardinal is a little unique in how it does environement management, providing 2 instances of the songbird library, one for staging and a second for production use. Both versions of the library have been included in the SDK so that there are no code changes required between environments.
In order to protect production the SDK will use the staging version by default. In order to switch the threeDS enabled actions over to production you need to provide the following in your options when creating the action.
{
threeDS: {
env: "prod"
}
}
Here is an example of the prod config being used to validate a card:
const enrollmentRequest: any = {
sessionId: CARD_CAPTURE_RESPONSE_TOKEN,
threeDS: {
env: "prod"
}
};
const action = this.framesSDK.createAction(FRAMES.ActionTypes.ValidateCard, enrollmentRequest);
If you wish to perform 3DS2 verification as part of a card capture exercise, you can do so by specifying that 3DS is required when initializing the card capture action.
const captureCardAction = this.framesSDK.createAction(
FRAMES.ActionTypes.ValidateCard,
{
threeDS: {
requires3DS: true
}
}
) as CaptureCard;
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJPcmdVbml0SWQiOiI2MGFmOGExZTBiYWM1ZDUwY2MyNmYzM2MiLCJSZWZlcmVuY2VJZCI6ImUxYzdjNzk4LWE1MjYtNDZhMC05ODU4LTRmNGIwMmNlNzdiOSIsImlzcyI6InBldGN1bHR1cmUiLCJQYXlsb2FkIjp7ImFjdGlvbklkIjoiYzYxZmM1OTgtZDU3ZS00MWM3LTg4YzQtMjhhODlkOTczYzEyIiwib3JkZXJJbmZvcm1hdGlvbiI6eyJhbW91bnREZXRhaWxzIjp7ImN1cnJlbmN5IjoiQVVEIn19fSwiaWF0IjoxNjI3NTE3MTc2LCJqdGkiOiIzNDMyMDBmMC0wNzQ3LTQ1NWUtODdlMi04ZTU5OTc3ZTAzMDEifQ.bghcu82uOuN6LSX_oKPj8f6WjBMhnXK3DYUkfp1F0mc",
"message": "3DS TOKEN REQUIRED"}
}
const enrollmentRequest: any = {
sessionId: cardCaptureResponse.token,
threeDS: {
env: "staging"
}
};
const action = this.framesSDK.createAction(FRAMES.ActionTypes.ValidateCard, enrollmentRequest);
await action.start();
action.createFramesControl('ValidateCard', 'yourElementId');
Typically the 3DS window is displayed as a modal, however hte IFrame can be embedded anywhere. To assist in building out your experience, we have 2 events:
These events can be subscribed to in the typical fashion:
const renderEventListener = () => {
// Do something on render
console.log('Show modal');
};
const closeEventListener = () => {
// Do something on close
console.log('Hide modal');
};
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
Complete the validateCard action. If successful this will return a challengeResponse that can be used to complete the captureCard action.
const validationResponse = await action.complete();
Here is an example reponse:
{
"threeDSData": {
"Validated": true,
"ActionCode": "SUCCESS",
"ErrorNumber": 0,
"ErrorDescription": "Success",
"Payment": {
"Type": "CCA",
"ExtendedData": {
"Amount": "0",
"CAVV": "MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=",
"CurrencyCode": "036",
"ECIFlag": "05",
"ThreeDSVersion": "2.1.0",
"PAResStatus": "Y",
"SignatureVerification": "Y"
},
"ProcessorTransactionId": "Rq6wpFnMVE9tMpRjuIC0"
}
},
"challengeResponse": {
"type": "3DS",
"instrumentId": undefined,
"token": "Rq6wpFnMVE9tMpRjuIC0",
"reference": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJPcmdVbml0SWQiOiI2MGFmOGExZTBiYWM1ZDUwY2MyNmYzM2MiLCJSZWZlcmVuY2VJZCI6ImE4N2VmMWM3LWE4ZjUtNGYzNy05MjY2LTQzMzE0MzNmNjJiOSIsImlzcyI6InBldGN1bHR1cmUiLCJQYXlsb2FkIjp7ImFjdGlvbklkIjoiODQwOTE1YTQtNjkzYS00YmQ3LTk1OTMtZGZjYWM0YjE4NjQ2Iiwib3JkZXJJbmZvcm1hdGlvbiI6eyJhbW91bnREZXRhaWxzIjp7ImN1cnJlbmN5IjoiQVVEIn19fSwiaWF0IjoxNjI3NTE5MzY1LCJqdGkiOiJkZmM4MWRiOC01YTA1LTQzMTUtODBmMy00NDAyNTZiZjA2MTgifQ.BIcz8Jk6cFYYSv872M1mCISEQqAvWJKDeDXv-2qF-ko"
}
}
const cardCaptureResponse = await this.captureCardAction.complete(this.saveCard, [validationResponse.challengeResponse]);
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
If 3DS has been requested as part of the payment flow then you will be required to provide a 3DS challenge response when attempting to make a payment. To create the challenge response, you need to create and execute the validatePayment action. This will orchestrate 3DS verifaction using the Cardinal Songbird library and return a chellengeResponse that can then be used when making a payment.
Please note, your schemaId may differ
const request = {
merchantReferenceId: 12345,
maxUses: 3,
timeToLivePayment: 300,
grossAmount: 2.40,
merchantPayload: {
schemaId: '0a221353-b26c-4848-9a77-4a8bcbacf228',
payload: {
requires3DS: settings.merchant.require3DSPA
}
}
};
return merchantSDK.payments.createPaymentRequest(request);
const transaction = await customerSDK.paymentRequests.makePayment(paymentRequestId, paymentInstrumentId);
Here is an example of rejected transaction with 3DS challenge:
{
"type": "PAYMENT",
"status": "REJECTED",
"rollback": "NOT_REQUIRED",
"merchantId": "petculture",
"grossAmount": 12.4,
"instruments": [
{
"transactions": [],
"instrumentType": "CREDIT_CARD",
"paymentInstrumentId": "198821"
}
],
"executionTime": "2021-07-29T01:53:33.517Z",
"transactionId": "9b5eaf73-30d8-4f32-aeb5-e1c4ac2a2a8c",
"clientReference": "9b5eaf73-30d8-4f32-aeb5-e1c4ac2a2a8c",
"subTransactions": [
{
"threeDS": {
"sessionId": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNGE5MjVhNi0wNzFmLTRiZjEtODA0MS1lOGJmNjEwYzQ4ZTgiLCJpYXQiOjE2Mjc1MjM2MTcuMDk4LCJpc3MiOiJwZXRjdWx0dXJlIiwiT3JnVW5pdElkIjoiNjBhZjhhMWUwYmFjNWQ1MGNjMjZmMzNjIiwiUGF5bG9hZCI6eyJwYXltZW50SW5zdHJ1bWVudElkIjoiMTk4ODIxIiwib3JkZXJJbmZvcm1hdGlvbiI6eyJhbW91bnREZXRhaWxzIjp7ImN1cnJlbmN5IjoiQVVEIiwiYW1vdW50IjoxMi40fX19LCJPYmplY3RpZnlQYXlsb2FkIjp0cnVlLCJSZWZlcmVuY2VJZCI6IjQ1OTA3MzQ5LWU2OTEtNDFkOS05Njk3LTgxYWFiMTc4MzZlZSJ9.W9D3yDqnGDZg3QncvVmiVfe7d8LW2se4yeS2jx7rPZQ",
"paymentInstrumentId": "198821"
},
"errorCode": "3DS_001",
"errorMessage": "3DS TOKEN REQUIRED"
}
],
"paymentRequestId": "34a925a6-071f-4bf1-8041-e8bf610c48e8",
"merchantReferenceId": "d0a118eb-613e-4899-8b71-70806abd40be"
}
const enrollmentRequest: any = {
sessionId, (Provided in the 3DS challenge)
paymentInstrumentId, (The payment instrumentID you want to perform 3DS on - must match instrument used in the challenge)
threeDS: {
env: "staging",
consumerAuthenticationInformation: {
acsWindowSize: this.acsWindowSize,
}
}
};
const action = this.framesSDK.createAction(FRAMES.ActionTypes.ValidatePayment, enrollmentRequest) as ValidatePayment;
await action.start();
action.createFramesControl('ValidatePayment', 'yourElementId');
Typically the 3DS window is displayed as a modal, however hte IFrame can be embedded anywhere. To assist in building out your experience, we have 2 events:
These events can be subscribed to in the typical fashion:
const renderEventListener = () => {
// Do something on render
console.log('Show modal');
};
const closeEventListener = () => {
// Do something on close
console.log('Hide modal');
};
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
Complete the action. If successful this will return a 3DS challenge response.
const validationResponse = await action.complete();
const transaction = await this.customerSDK.paymentRequests.makePayment(paymentRequestId, paymentInstrumentId, [], undefined, undefined, undefined, [validationResponse.challengeResponse]);
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
At times you may want to leverage additional config items made available by cybersource, such as flags to force 3DS step up or specify a window size. These additional config values can be found in the consumerAuthenticationInformation
block.
Here is an example of the configuration you would use when creating a validateCard action to use the consumerAuthenticationInformation
to set the window size:
{
sessionId: "YOUR_SESSION_ID",
threeDS: {
consumerAuthenticationInformation: {
acsWindowSize: "01",
}
}
}
For a full list of options (and descriptions), check out the cybersource documentation for the "Check Payer Auth Enrollment" endpoint. The request payload contains the property consumerAuthenticationInformation
which is the property exposed above.
Cybersource docs - https://developer.cybersource.com/api-reference-assets/index.html#payer-authentication_payer-authentication_check-payer-auth-enrollment
FAQs
Development kit to assist in building clients that use the woolies API
We found that @wpay/frames demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.